diff --git a/headers/private/interface/ShapePrivate.h b/headers/private/interface/ShapePrivate.h index 9fa63448ae..31e201e2d5 100644 --- a/headers/private/interface/ShapePrivate.h +++ b/headers/private/interface/ShapePrivate.h @@ -9,6 +9,13 @@ #ifndef SHAPE_PRIVATE_H #define SHAPE_PRIVATE_H +#include +#include +#include + +#include +#include + #define OP_LINETO 0x10000000 #define OP_BEZIERTO 0x20000000 @@ -20,14 +27,43 @@ #define OP_SMALL_ARC_TO_CCW 0x08000000 -struct shape_data { +struct shape_data : public BReferenceable { uint32* opList; + BPoint* ptList; int32 opCount; int32 opSize; - BPoint* ptList; int32 ptCount; int32 ptSize; + bool fOwnsMemory; + + shape_data() + : + fOwnsMemory(false) + { + } + + ~shape_data() + { + if (fOwnsMemory) { + delete[] opList; + delete[] ptList; + } + } + + shape_data(const shape_data& other) + { + opList = new(std::nothrow) uint32[other.opCount]; + ptList = new(std::nothrow) BPoint[other.ptCount]; + fOwnsMemory = true; + opCount = other.opCount; + opSize = other.opSize; + ptCount = other.ptCount; + ptSize = other.ptSize; + memcpy(opList, other.opList, opSize); + memcpy(ptList, other.ptList, ptSize); + } + BRect DetermineBoundingBox() const { BRect bounds; diff --git a/src/servers/app/DrawState.cpp b/src/servers/app/DrawState.cpp index 0e81463b1f..a0c61e0c53 100644 --- a/src/servers/app/DrawState.cpp +++ b/src/servers/app/DrawState.cpp @@ -515,8 +515,9 @@ DrawState::ClipToShape(shape_data* shape, bool inverse) if (!fCombinedTransform.IsIdentity()) fCombinedTransform.Apply(shape->ptList, shape->ptCount); - AlphaMask* const mask = new ShapeAlphaMask(GetAlphaMask(), *shape, + AlphaMask* const mask = ShapeAlphaMask::Create(GetAlphaMask(), *shape, BPoint(0, 0), inverse); + SetAlphaMask(mask); if (mask != NULL) mask->ReleaseReference(); diff --git a/src/servers/app/ServerWindow.cpp b/src/servers/app/ServerWindow.cpp index d91225e443..5e0531f583 100644 --- a/src/servers/app/ServerWindow.cpp +++ b/src/servers/app/ServerWindow.cpp @@ -2086,11 +2086,12 @@ fDesktop->LockSingleWindow(); shape_data shape; link.Read(&shape.opCount); link.Read(&shape.ptCount); + shape.opSize = shape.opCount * sizeof(uint32); + shape.ptSize = shape.ptCount * sizeof(BPoint); shape.opList = new(nothrow) uint32[shape.opCount]; shape.ptList = new(nothrow) BPoint[shape.ptCount]; - if (link.Read(shape.opList, shape.opCount * sizeof(uint32)) >= B_OK - && link.Read(shape.ptList, - shape.ptCount * sizeof(BPoint)) >= B_OK) { + if (link.Read(shape.opList, shape.opSize) >= B_OK + && link.Read(shape.ptList, shape.ptSize) >= B_OK) { fCurrentView->ClipToShape(&shape, inverse); _UpdateDrawState(fCurrentView); } @@ -3044,6 +3045,7 @@ ServerWindow::_DispatchViewDrawingMessage(int32 code, { DTRACE(("ServerWindow %s: Message AS_VIEW_END_LAYER\n", Title())); + fCurrentView->BlendAllLayers(); fCurrentView->SetPicture(NULL); fCurrentView->CurrentState()->SetDrawingModeLocked(false); @@ -3573,11 +3575,12 @@ ServerWindow::_DispatchPictureMessage(int32 code, BPrivate::LinkReceiver& link) shape_data shape; link.Read(&shape.opCount); link.Read(&shape.ptCount); + shape.opSize = shape.opCount * sizeof(uint32); + shape.ptSize = shape.ptCount * sizeof(BPoint); shape.opList = new(nothrow) uint32[shape.opCount]; shape.ptList = new(nothrow) BPoint[shape.ptCount]; - if (link.Read(shape.opList, shape.opCount * sizeof(uint32)) >= B_OK - && link.Read(shape.ptList, - shape.ptCount * sizeof(BPoint)) >= B_OK) { + if (link.Read(shape.opList, shape.opSize) >= B_OK + && link.Read(shape.ptList, shape.ptSize) >= B_OK) { picture->WriteClipToShape(shape.opCount, shape.opList, shape.ptCount, shape.ptList, inverse); } diff --git a/src/servers/app/drawing/AlphaMask.cpp b/src/servers/app/drawing/AlphaMask.cpp index 35da811034..e7601dc96d 100644 --- a/src/servers/app/drawing/AlphaMask.cpp +++ b/src/servers/app/drawing/AlphaMask.cpp @@ -11,6 +11,7 @@ #include "AlphaMask.h" +#include "AlphaMaskCache.h" #include "BitmapHWInterface.h" #include "BitmapManager.h" #include "Canvas.h" @@ -34,11 +35,39 @@ AlphaMask::AlphaMask(AlphaMask* previousMask, bool inverse) fCanvasBounds(), fInverse(inverse), fBackgroundOpacity(0), + fNextMaskCount(0), + fInCache(false), + fIndirectCacheReferences(0), fBits(NULL), fBuffer(), fMask(), fScanline(fMask) { + if (previousMask != NULL) + atomic_add(&previousMask->fNextMaskCount, 1); +} + + +AlphaMask::AlphaMask(AlphaMask* previousMask, AlphaMask* other) + : + fPreviousMask(previousMask), + fBounds(other->fBounds), + fClippedToCanvas(other->fClippedToCanvas), + fCanvasOrigin(other->fCanvasOrigin), + fCanvasBounds(other->fCanvasBounds), + fInverse(other->fInverse), + fBackgroundOpacity(other->fBackgroundOpacity), + fNextMaskCount(0), + fInCache(false), + fIndirectCacheReferences(0), + fBits(other->fBits), + fBuffer(other->fBuffer), + fMask(other->fMask), + fScanline(fMask) +{ + if (previousMask != NULL) + atomic_add(&previousMask->fNextMaskCount, 1); + fBits->AcquireReference(); } @@ -51,6 +80,9 @@ AlphaMask::AlphaMask(uint8 backgroundOpacity) fCanvasBounds(), fInverse(false), fBackgroundOpacity(backgroundOpacity), + fNextMaskCount(0), + fInCache(false), + fIndirectCacheReferences(0), fBits(NULL), fBuffer(), fMask(), @@ -61,7 +93,10 @@ AlphaMask::AlphaMask(uint8 backgroundOpacity) AlphaMask::~AlphaMask() { - delete[] fBits; + if (fBits != NULL) + fBits->ReleaseReference(); + if (fPreviousMask.Get() != NULL) + atomic_add(&fPreviousMask->fNextMaskCount, -1); } @@ -95,6 +130,13 @@ AlphaMask::SetCanvasGeometry(IntPoint origin, IntRect bounds) } +size_t +AlphaMask::BitmapSize() const +{ + return fBits->BitsLength(); +} + + ServerBitmap* AlphaMask::_CreateTemporaryBitmap(BRect bounds) const { @@ -124,14 +166,16 @@ AlphaMask::_Generate() return; } - const int32 width = fBounds.IntegerWidth() + 1; - const int32 height = fBounds.IntegerHeight() + 1; - - delete[] fBits; - fBits = new(std::nothrow) uint8[width * height]; + if (fBits != NULL) + fBits->ReleaseReference(); + fBits = new(std::nothrow) UtilityBitmap(fBounds, B_GRAY8, 0); + if (fBits == NULL) + return; + const int32 width = fBits->Width(); + const int32 height = fBits->Height(); uint8* source = bitmap->Bits(); - uint8* destination = fBits; + uint8* destination = fBits->Bits(); uint32 numPixels = width * height; if (fPreviousMask != NULL) { @@ -159,8 +203,10 @@ AlphaMask::_Generate() } } - fBuffer.attach(fBits, width, height, width); + fBuffer.attach(fBits->Bits(), width, height, width); _AttachMaskToBuffer(); + + _AddToCache(); } @@ -228,6 +274,12 @@ UniformAlphaMask::_Offset() } +void +UniformAlphaMask::_AddToCache() +{ +} + + // #pragma mark - VectorAlphaMask @@ -241,6 +293,16 @@ VectorAlphaMask::VectorAlphaMask(AlphaMask* previousMask, } +template +VectorAlphaMask::VectorAlphaMask(AlphaMask* previousMask, + VectorAlphaMask* other) + : + AlphaMask(previousMask, other), + fWhere(other->fWhere) +{ +} + + template ServerBitmap* VectorAlphaMask::_RenderSource(const IntRect& canvasBounds) @@ -291,6 +353,7 @@ VectorAlphaMask::_RenderSource(const IntRect& canvasBounds) engine->UnlockParallelAccess(); } + canvas.PopState(); delete engine; return bitmap; @@ -360,17 +423,73 @@ PictureAlphaMask::GetDrawState() const } +void +PictureAlphaMask::_AddToCache() +{ + // currently not implemented +} + + // #pragma mark - ShapeAlphaMask +DrawState* ShapeAlphaMask::fDrawState = NULL; + + ShapeAlphaMask::ShapeAlphaMask(AlphaMask* previousMask, const shape_data& shape, BPoint where, bool inverse) : VectorAlphaMask(previousMask, where, inverse), - fShape(shape), + fShape(new shape_data(shape)), fDrawState() { - fShapeBounds = fShape.DetermineBoundingBox(); + if (fDrawState == NULL) + fDrawState = new(std::nothrow) DrawState(); + + fShapeBounds = fShape->DetermineBoundingBox(); +} + + +ShapeAlphaMask::ShapeAlphaMask(AlphaMask* previousMask, + ShapeAlphaMask* other) + : + VectorAlphaMask(previousMask, other), + fShape(other->fShape), + fShapeBounds(other->fShapeBounds) +{ + fShape->AcquireReference(); +} + + +ShapeAlphaMask::~ShapeAlphaMask() +{ + fShape->ReleaseReference(); +} + + +/* static */ ShapeAlphaMask* +ShapeAlphaMask::Create(AlphaMask* previousMask, const shape_data& shape, + BPoint where, bool inverse) +{ + // Look if we have a suitable cached mask + ShapeAlphaMask* mask = AlphaMaskCache::Default()->Get(shape, previousMask, + inverse); + + if (mask == NULL) { + // No cached mask, create new one + mask = new(std::nothrow) ShapeAlphaMask(previousMask, shape, + BPoint(0, 0), inverse); + } else { + // Create new mask which reuses the parameters and the mask bitmap + // of the cache entry + // TODO: don't make a new mask if the cache entry has no drawstate + // using it anymore, because then we ca just immediately reuse it + AlphaMask* cachedMask = mask; + mask = new(std::nothrow) ShapeAlphaMask(previousMask, mask); + cachedMask->ReleaseReference(); + } + + return mask; } @@ -378,8 +497,8 @@ void ShapeAlphaMask::DrawVectors(Canvas* canvas) { canvas->GetDrawingEngine()->DrawShape(fBounds, - fShape.opCount, fShape.opList, - fShape.ptCount, fShape.ptList, + fShape->opCount, fShape->opList, + fShape->ptCount, fShape->ptList, true, BPoint(0, 0), 1.0); } @@ -394,5 +513,12 @@ ShapeAlphaMask::DetermineBoundingBox() const const DrawState& ShapeAlphaMask::GetDrawState() const { - return fDrawState; + return *fDrawState; +} + + +void +ShapeAlphaMask::_AddToCache() +{ + AlphaMaskCache::Default()->Put(this); } diff --git a/src/servers/app/drawing/AlphaMask.h b/src/servers/app/drawing/AlphaMask.h index 592f486cb4..7d53d67c53 100644 --- a/src/servers/app/drawing/AlphaMask.h +++ b/src/servers/app/drawing/AlphaMask.h @@ -20,6 +20,7 @@ class BShape; class ServerBitmap; class ServerPicture; class shape_data; +class UtilityBitmap; // #pragma mark - AlphaMask @@ -29,6 +30,8 @@ class AlphaMask : public BReferenceable { public: AlphaMask(AlphaMask* previousMask, bool inverse); + AlphaMask(AlphaMask* previousMask, + AlphaMask* other); AlphaMask(uint8 backgroundOpacity); virtual ~AlphaMask(); @@ -41,11 +44,14 @@ public: agg::clipped_alpha_mask* Mask() { return &fMask; } + size_t BitmapSize() const; + protected: ServerBitmap* _CreateTemporaryBitmap(BRect bounds) const; void _Generate(); void _SetNoClipping(); const IntRect& _PreviousMaskBounds() const; + virtual void _AddToCache() = 0; private: virtual ServerBitmap* _RenderSource(const IntRect& canvasBounds) = 0; @@ -59,12 +65,22 @@ protected: bool fClippedToCanvas; private: + friend class AlphaMaskCache; + IntPoint fCanvasOrigin; IntRect fCanvasBounds; const bool fInverse; uint8 fBackgroundOpacity; - uint8* fBits; + int32 fNextMaskCount; + bool fInCache; + uint32 fIndirectCacheReferences; + // number of times this mask has been + // seen as "previous mask" of another + // one in the cache, without being + // in the cache itself + + UtilityBitmap* fBits; agg::rendering_buffer fBuffer; agg::clipped_alpha_mask fMask; scanline_unpacked_masked_type fScanline; @@ -78,6 +94,7 @@ public: private: virtual ServerBitmap* _RenderSource(const IntRect& canvasBounds); virtual IntPoint _Offset(); + virtual void _AddToCache(); }; @@ -89,6 +106,8 @@ class VectorAlphaMask : public AlphaMask { public: VectorAlphaMask(AlphaMask* previousMask, BPoint where, bool inverse); + VectorAlphaMask(AlphaMask* previousMask, + VectorAlphaMask* other); private: virtual ServerBitmap* _RenderSource(const IntRect& canvasBounds); @@ -114,6 +133,9 @@ public: BRect DetermineBoundingBox() const; const DrawState& GetDrawState() const; +private: + virtual void _AddToCache(); + private: BReference fPicture; DrawState* fDrawState; @@ -124,19 +146,33 @@ private: class ShapeAlphaMask : public VectorAlphaMask { -public: +private: ShapeAlphaMask(AlphaMask* previousMask, const shape_data& shape, BPoint where, bool inverse); + ShapeAlphaMask(AlphaMask* previousMask, + ShapeAlphaMask* other); + +public: + virtual ~ShapeAlphaMask(); + + static ShapeAlphaMask* Create(AlphaMask* previousMask, + const shape_data& shape, + BPoint where, bool inverse); void DrawVectors(Canvas* canvas); BRect DetermineBoundingBox() const; const DrawState& GetDrawState() const; private: - const shape_data& fShape; + virtual void _AddToCache(); + +private: + friend class AlphaMaskCache; + + shape_data* fShape; BRect fShapeBounds; - DrawState fDrawState; + static DrawState* fDrawState; }; diff --git a/src/servers/app/drawing/AlphaMaskCache.cpp b/src/servers/app/drawing/AlphaMaskCache.cpp new file mode 100644 index 0000000000..dcaa212a1f --- /dev/null +++ b/src/servers/app/drawing/AlphaMaskCache.cpp @@ -0,0 +1,190 @@ +/* + * Copyright 2015 Julian Harnath + * All rights reserved. Distributed under the terms of the MIT license. + */ + +#include "AlphaMaskCache.h" + +#include "AlphaMask.h" +#include "ShapePrivate.h" + +#include + + +//#define PRINT_ALPHA_MASK_CACHE_STATISTICS +#ifdef PRINT_ALPHA_MASK_CACHE_STATISTICS +static uint32 sAlphaMaskGetCount = 0; +#endif + + +AlphaMaskCache AlphaMaskCache::sDefaultInstance; + + +AlphaMaskCache::AlphaMaskCache() + : + fLock("AlphaMask cache"), + fCurrentCacheBytes(0), + fTooLargeMaskCount(0), + fMasksReplacedCount(0), + fHitCount(0), + fMissCount(0), + fLowerMaskReferencedCount(0) +{ +} + + +AlphaMaskCache::~AlphaMaskCache() +{ + Clear(); +} + + +/* static */ AlphaMaskCache* +AlphaMaskCache::Default() +{ + return &sDefaultInstance; +} + + +status_t +AlphaMaskCache::Put(ShapeAlphaMask* mask) +{ + AutoLocker locker(fLock); + + size_t maskStackSize = mask->BitmapSize(); + maskStackSize += _FindUncachedPreviousMasks(mask, true); + + if (maskStackSize > kMaxCacheBytes) { + _FindUncachedPreviousMasks(mask, false); + fTooLargeMaskCount++; + return B_NO_MEMORY; + } + + if (fCurrentCacheBytes + maskStackSize > kMaxCacheBytes) { + for (ShapeMaskSet::iterator it = fShapeMasks.begin(); + it != fShapeMasks.end();) { + + if (atomic_get(&it->fMask->fNextMaskCount) > 0) { + it++; + continue; + } + + size_t removedMaskStackSize = it->fMask->BitmapSize(); + removedMaskStackSize += _FindUncachedPreviousMasks(it->fMask, + false); + fCurrentCacheBytes -= removedMaskStackSize; + + it->fMask->fInCache = false; + it->fMask->ReleaseReference(); + fMasksReplacedCount++; + fShapeMasks.erase(it++); + + if (fCurrentCacheBytes + maskStackSize <= kMaxCacheBytes) + break; + } + } + + if (fCurrentCacheBytes + maskStackSize > kMaxCacheBytes) { + _FindUncachedPreviousMasks(mask, false); + fTooLargeMaskCount++; + return B_NO_MEMORY; + } + + fCurrentCacheBytes += maskStackSize; + + ShapeMaskElement element(mask->fShape, mask, mask->fPreviousMask.Get(), + mask->fInverse); + fShapeMasks.insert(element); + mask->AcquireReference(); + mask->fInCache = true; + return B_OK; +} + + +ShapeAlphaMask* +AlphaMaskCache::Get(const shape_data& shape, AlphaMask* previousMask, + bool inverse) +{ + AutoLocker locker(fLock); + +#ifdef PRINT_ALPHA_MASK_CACHE_STATISTICS + if (sAlphaMaskGetCount++ > 200) { + _PrintAndResetStatistics(); + sAlphaMaskGetCount = 0; + } +#endif + + ShapeMaskElement element(&shape, NULL, previousMask, inverse); + ShapeMaskSet::iterator it = fShapeMasks.find(element); + if (it == fShapeMasks.end()) { + fMissCount++; + return NULL; + } + fHitCount++; + it->fMask->AcquireReference(); + return it->fMask; +} + + +void +AlphaMaskCache::Clear() +{ + AutoLocker locker(fLock); + + for (ShapeMaskSet::iterator it = fShapeMasks.begin(); + it != fShapeMasks.end(); it++) { + it->fMask->fInCache = false; + it->fMask->fIndirectCacheReferences = 0; + it->fMask->ReleaseReference(); + } + fShapeMasks.clear(); + fTooLargeMaskCount = 0; + fMasksReplacedCount = 0; + fHitCount = 0; + fMissCount = 0; + fLowerMaskReferencedCount = 0; +} + + +size_t +AlphaMaskCache::_FindUncachedPreviousMasks(AlphaMask* mask, bool reference) +{ + const int32 referenceModifier = reference ? 1 : -1; + size_t addedOrRemovedSize = 0; + + for (AlphaMask* lowerMask = mask->fPreviousMask.Get(); lowerMask != NULL; + lowerMask = lowerMask->fPreviousMask.Get()) { + if (lowerMask->fInCache) + continue; + uint32 oldReferences = lowerMask->fIndirectCacheReferences; + lowerMask->fIndirectCacheReferences += referenceModifier; + if (lowerMask->fIndirectCacheReferences == 0 || oldReferences == 0) { + // We either newly referenced the mask for the first time, or + // released the last reference + addedOrRemovedSize += lowerMask->BitmapSize(); + fLowerMaskReferencedCount += referenceModifier; + } + } + + return addedOrRemovedSize; +} + + +void +AlphaMaskCache::_PrintAndResetStatistics() +{ + debug_printf("AlphaMaskCache statistics: size=%4ld bytes=%4ld lower=%4ld " + "total=%4ld too_large=%4ld replaced=%4ld hit=%4ld miss=%4ld\n", + fShapeMasks.size(), + fCurrentCacheBytes, + fLowerMaskReferencedCount, + fShapeMasks.size() + fLowerMaskReferencedCount, + fTooLargeMaskCount, + fMasksReplacedCount, + fHitCount, + fMissCount); + fTooLargeMaskCount = 0; + fMasksReplacedCount = 0; + fHitCount = 0; + fMissCount = 0; +} diff --git a/src/servers/app/drawing/AlphaMaskCache.h b/src/servers/app/drawing/AlphaMaskCache.h new file mode 100644 index 0000000000..495d469b85 --- /dev/null +++ b/src/servers/app/drawing/AlphaMaskCache.h @@ -0,0 +1,106 @@ +/* + * Copyright 2015 Julian Harnath + * All rights reserved. Distributed under the terms of the MIT license. + */ +#ifndef ALPHA_MASK_CACHE_H +#define ALPHA_MASK_CACHE_H + +#include + +#include "ShapePrivate.h" +#include +#include + + +class AlphaMask; +class ShapeAlphaMask; + + +class AlphaMaskCache { +private: + enum { + kMaxCacheBytes = 8 * 1024 * 1024 // 8 MiB + }; + +public: + AlphaMaskCache(); + ~AlphaMaskCache(); + + static AlphaMaskCache* Default(); + + status_t Put(ShapeAlphaMask* mask); + ShapeAlphaMask* Get(const shape_data& shape, + AlphaMask* previousMask, + bool inverse); + + void Clear(); + +private: + size_t _FindUncachedPreviousMasks(AlphaMask* mask, + bool reference); + void _PrintAndResetStatistics(); + +private: + struct ShapeMaskElement { + ShapeMaskElement(const shape_data* shape, + ShapeAlphaMask* mask, AlphaMask* previousMask, + bool inverse) + : + fShape(shape), + fInverse(inverse), + fMask(mask), + fPreviousMask(previousMask) + { + } + + bool operator<(const ShapeMaskElement& other) const + { + if (fInverse != other.fInverse) + return fInverse < other.fInverse; + if (fPreviousMask != other.fPreviousMask) + return fPreviousMask < other.fPreviousMask; + + // compare shapes + if (fShape->ptCount != other.fShape->ptCount) + return fShape->ptCount < other.fShape->ptCount; + if (fShape->opCount != other.fShape->opCount) + return fShape->opCount < other.fShape->opCount; + int diff = memcmp(fShape->ptList, other.fShape->ptList, + fShape->ptSize); + if (diff != 0) + return diff < 0; + diff = memcmp(fShape->opList, other.fShape->opList, + fShape->opSize); + if (diff != 0) + return diff < 0; + + // equal + return false; + } + + const shape_data* fShape; + bool fInverse; + ShapeAlphaMask* fMask; + AlphaMask* fPreviousMask; + }; + +private: + typedef std::set ShapeMaskSet; + + static AlphaMaskCache sDefaultInstance; + + BLocker fLock; + + size_t fCurrentCacheBytes; + ShapeMaskSet fShapeMasks; + + // Statistics counters + uint32 fTooLargeMaskCount; + uint32 fMasksReplacedCount; + uint32 fHitCount; + uint32 fMissCount; + uint32 fLowerMaskReferencedCount; +}; + + +#endif // ALPHA_MASK_CACHE_H diff --git a/src/servers/app/drawing/Jamfile b/src/servers/app/drawing/Jamfile index 0893dfb118..0a3eb39109 100644 --- a/src/servers/app/drawing/Jamfile +++ b/src/servers/app/drawing/Jamfile @@ -17,6 +17,7 @@ Includes [ FGristFiles AlphaMask.cpp DrawingEngine.cpp ] StaticLibrary libasdrawing.a : AlphaMask.cpp + AlphaMaskCache.cpp BitmapBuffer.cpp BitmapDrawingEngine.cpp drawing_support.cpp