diff --git a/src/tests/servers/app/newerClipping/ClientLooper.cpp b/src/tests/servers/app/newerClipping/ClientLooper.cpp index 20d8148586..9ada6a0473 100644 --- a/src/tests/servers/app/newerClipping/ClientLooper.cpp +++ b/src/tests/servers/app/newerClipping/ClientLooper.cpp @@ -13,7 +13,7 @@ // constructor ClientLooper::ClientLooper(const char* name, WindowLayer* serverWindow) - : BLooper(""), + : BLooper("", B_DISPLAY_PRIORITY), fServerWindow(serverWindow), fViewCount(0) { diff --git a/src/tests/servers/app/newerClipping/Desktop.cpp b/src/tests/servers/app/newerClipping/Desktop.cpp index e10278adff..d902cac2ed 100644 --- a/src/tests/servers/app/newerClipping/Desktop.cpp +++ b/src/tests/servers/app/newerClipping/Desktop.cpp @@ -231,12 +231,15 @@ Desktop::MessageReceived(BMessage* message) if (message->FindRect("area", &area) >= B_OK) Draw(area); } + + case MSG_ADD_WINDOW: { WindowLayer* window; if (message->FindPointer("window", (void**)&window) >= B_OK) AddWindow(window); break; } + default: BLooper::MessageReceived(message); } @@ -570,6 +573,16 @@ if (fDrawingEngine->Lock()) { void Desktop::MarkClean(BRegion* region) { + // NOTE: when a window owns the read lock for + // the clipping, it means the Desktop thread is currently + // not messing with the regions. Since a window is expected + // to only remove parts from the dirty region that intersect + // it's own visible region, it cannot remove the wrong parts + // of the dirty region (ie parts intersecting another window) + // writelocking would therefor not be needed, but of course + // BRegion is not threadsafe, and therefor, it would be cool + // if the dirty region was moved into each window again. + if (LockClipping()) { // remove the clean region from the culmulative dirty region fDirtyRegion.Exclude(region); diff --git a/src/tests/servers/app/newerClipping/Desktop.h b/src/tests/servers/app/newerClipping/Desktop.h index bc2e6f7dd5..69fdead71d 100644 --- a/src/tests/servers/app/newerClipping/Desktop.h +++ b/src/tests/servers/app/newerClipping/Desktop.h @@ -3,24 +3,34 @@ #define DESKTOP_H #include -#include #include #include #include #include "DrawingEngine.h" -#include "MultiLocker.h" + +#define SHOW_GLOBAL_DIRTY_REGION 0 +#define SHOW_WINDOW_CONTENT_DIRTY_REGION 0 + +#define MULTI_LOCKER 0 +#define RW_LOCKER 0 + +#if MULTI_LOCKER +# include "MultiLocker.h" +#elif RW_LOCKER +# include "RWLocker.h" +#else +# include +#endif class WindowLayer; enum { MSG_ADD_WINDOW = 'addw', MSG_DRAW = 'draw', -}; -#define MULTI_LOCKER 0 -#define SHOW_GLOBAL_DIRTY_REGION 0 -#define SHOW_WINDOW_CONTENT_DIRTY_REGION 0 + MSG_MARK_CLEAN = 'mcln', +}; class Desktop : public BLooper { public: @@ -58,22 +68,29 @@ class Desktop : public BLooper { void SetFocusWindow(WindowLayer* window); -#if MULTI_LOCKER -# if 0 +#if RW_LOCKER bool ReadLockClipping() { return fClippingLock.ReadLock(); } + bool ReadLockClippingWithTimeout() { return fClippingLock.ReadLockWithTimeout(10000) >= B_OK; } void ReadUnlockClipping() { fClippingLock.ReadUnlock(); } -# else - bool ReadLockClipping() { return fClippingLock.WriteLock(); } - void ReadUnlockClipping() { fClippingLock.WriteUnlock(); } -# endif -#else + + bool LockClipping() { return fClippingLock.WriteLock(); } + void UnlockClipping() { fClippingLock.WriteUnlock(); } +#elif MULTI_LOCKER + bool ReadLockClipping() { return fClippingLock.ReadLock(); } + bool ReadLockClippingWithTimeout() { return fClippingLock.ReadLock(); } + void ReadUnlockClipping() { fClippingLock.ReadUnlock(); } + + bool LockClipping() { return fClippingLock.WriteLock(); } + void UnlockClipping() { fClippingLock.WriteUnlock(); } +#else // BLocker bool ReadLockClipping() { return fClippingLock.Lock(); } bool ReadLockClippingWithTimeout() { return fClippingLock.LockWithTimeout(10000) >= B_OK; } void ReadUnlockClipping() { fClippingLock.Unlock(); } -#endif bool LockClipping() { return fClippingLock.Lock(); } void UnlockClipping() { fClippingLock.Unlock(); } +#endif + void MarkDirty(BRegion* region); void MarkClean(BRegion* region); @@ -100,6 +117,8 @@ private: #if MULTI_LOCKER MultiLocker fClippingLock; +#elif RW_LOCKER + RWLocker fClippingLock; #else BLocker fClippingLock; #endif diff --git a/src/tests/servers/app/newerClipping/RWLocker.cpp b/src/tests/servers/app/newerClipping/RWLocker.cpp new file mode 100644 index 0000000000..236d80413e --- /dev/null +++ b/src/tests/servers/app/newerClipping/RWLocker.cpp @@ -0,0 +1,465 @@ +// RWLocker.cpp + +#include + +#include "RWLocker.h" + +// info about a read lock owner +struct RWLocker::ReadLockInfo { + thread_id reader; + int32 count; +}; + + +// constructor +RWLocker::RWLocker() + : fLock(), + fMutex(), + fQueue(), + fReaderCount(0), + fWriterCount(0), + fReadLockInfos(8), + fWriter(B_ERROR), + fWriterWriterCount(0), + fWriterReaderCount(0) +{ + _Init(NULL); +} + +// constructor +RWLocker::RWLocker(const char* name) + : fLock(name), + fMutex(), + fQueue(), + fReaderCount(0), + fWriterCount(0), + fReadLockInfos(8), + fWriter(B_ERROR), + fWriterWriterCount(0), + fWriterReaderCount(0) +{ + _Init(name); +} + +// destructor +RWLocker::~RWLocker() +{ + fLock.Lock(); + delete_sem(fMutex.semaphore); + delete_sem(fQueue.semaphore); + for (int32 i = 0; ReadLockInfo* info = _ReadLockInfoAt(i); i++) + delete info; +} + +// ReadLock +bool +RWLocker::ReadLock() +{ + status_t error = _ReadLock(B_INFINITE_TIMEOUT); + return (error == B_OK); +} + +// ReadLockWithTimeout +status_t +RWLocker::ReadLockWithTimeout(bigtime_t timeout) +{ + bigtime_t absoluteTimeout = system_time() + timeout; + // take care of overflow + if (timeout > 0 && absoluteTimeout < 0) + absoluteTimeout = B_INFINITE_TIMEOUT; + return _ReadLock(absoluteTimeout); +} + +// ReadUnlock +void +RWLocker::ReadUnlock() +{ + if (fLock.Lock()) { + thread_id thread = find_thread(NULL); + if (thread == fWriter) { + // We (also) have a write lock. + if (fWriterReaderCount > 0) + fWriterReaderCount--; + // else: error: unmatched ReadUnlock() + } else { + int32 index = _IndexOf(thread); + if (ReadLockInfo* info = _ReadLockInfoAt(index)) { + fReaderCount--; + if (--info->count == 0) { + // The outer read lock bracket for the thread has been + // reached. Dispose the info. + _DeleteReadLockInfo(index); + } + if (fReaderCount == 0) { + // The last reader needs to unlock the mutex. + _ReleaseBenaphore(fMutex); + } + } // else: error: caller has no read lock + } + fLock.Unlock(); + } // else: we are probably going to be destroyed +} + +// IsReadLocked +// +// Returns whether or not the calling thread owns a read lock or even a +// write lock. +bool +RWLocker::IsReadLocked() const +{ + bool result = false; + if (fLock.Lock()) { + thread_id thread = find_thread(NULL); + result = (thread == fWriter || _IndexOf(thread) >= 0); + fLock.Unlock(); + } + return result; +} + +// WriteLock +bool +RWLocker::WriteLock() +{ + status_t error = _WriteLock(B_INFINITE_TIMEOUT); + return (error == B_OK); +} + +// WriteLockWithTimeout +status_t +RWLocker::WriteLockWithTimeout(bigtime_t timeout) +{ + bigtime_t absoluteTimeout = system_time() + timeout; + // take care of overflow + if (timeout > 0 && absoluteTimeout < 0) + absoluteTimeout = B_INFINITE_TIMEOUT; + return _WriteLock(absoluteTimeout); +} + +// WriteUnlock +void +RWLocker::WriteUnlock() +{ + if (fLock.Lock()) { + thread_id thread = find_thread(NULL); + if (thread == fWriter) { + fWriterCount--; + if (--fWriterWriterCount == 0) { + // The outer write lock bracket for the thread has been + // reached. + fWriter = B_ERROR; + if (fWriterReaderCount > 0) { + // We still own read locks. + _NewReadLockInfo(thread, fWriterReaderCount); + // A reader that expects to be the first reader may wait + // at the mutex semaphore. We need to wake it up. + if (fReaderCount > 0) + _ReleaseBenaphore(fMutex); + fReaderCount += fWriterReaderCount; + fWriterReaderCount = 0; + } else { + // We don't own any read locks. So we have to release the + // mutex benaphore. + _ReleaseBenaphore(fMutex); + } + } + } // else: error: unmatched WriteUnlock() + fLock.Unlock(); + } // else: We're probably going to die. +} + +// IsWriteLocked +// +// Returns whether or not the calling thread owns a write lock. +bool +RWLocker::IsWriteLocked() const +{ + return (fWriter == find_thread(NULL)); +} + +// _Init +void +RWLocker::_Init(const char* name) +{ + // init the mutex benaphore + BString mutexName(name); + mutexName += "_RWLocker_mutex"; + fMutex.semaphore = create_sem(0, mutexName.String()); + fMutex.counter = 0; + // init the queueing benaphore + BString queueName(name); + queueName += "_RWLocker_queue"; + fQueue.semaphore = create_sem(0, queueName.String()); + fQueue.counter = 0; +} + +// _ReadLock +// +// /timeout/ -- absolute timeout +status_t +RWLocker::_ReadLock(bigtime_t timeout) +{ + status_t error = B_OK; + thread_id thread = find_thread(NULL); + bool locked = false; + if (fLock.Lock()) { + // Check, if we already own a read (or write) lock. In this case we + // can skip the usual locking procedure. + if (thread == fWriter) { + // We already own a write lock. + fWriterReaderCount++; + locked = true; + } else if (ReadLockInfo* info = _ReadLockInfoAt(_IndexOf(thread))) { + // We already own a read lock. + info->count++; + fReaderCount++; + locked = true; + } + fLock.Unlock(); + } else // failed to lock the data + error = B_ERROR; + // Usual locking, i.e. we do not already own a read or write lock. + if (error == B_OK && !locked) { + error = _AcquireBenaphore(fQueue, timeout); + if (error == B_OK) { + if (fLock.Lock()) { + bool firstReader = false; + if (++fReaderCount == 1) { + // We are the first reader. + _NewReadLockInfo(thread); + firstReader = true; + } else + _NewReadLockInfo(thread); + fLock.Unlock(); + // The first reader needs to lock the mutex. + if (firstReader) { + error = _AcquireBenaphore(fMutex, timeout); + switch (error) { + case B_OK: + // fine + break; + case B_TIMED_OUT: { + // clean up + if (fLock.Lock()) { + _DeleteReadLockInfo(_IndexOf(thread)); + fReaderCount--; + fLock.Unlock(); + } + break; + } + default: + // Probably we are going to be destroyed. + break; + } + } + // Let the next candidate enter the game. + _ReleaseBenaphore(fQueue); + } else { + // We couldn't lock the data, which can only happen, if + // we're going to be destroyed. + error = B_ERROR; + } + } + } + return error; +} + +// _WriteLock +// +// /timeout/ -- absolute timeout +status_t +RWLocker::_WriteLock(bigtime_t timeout) +{ + status_t error = B_ERROR; + if (fLock.Lock()) { + bool infiniteTimeout = (timeout == B_INFINITE_TIMEOUT); + bool locked = false; + int32 readerCount = 0; + thread_id thread = find_thread(NULL); + int32 index = _IndexOf(thread); + if (ReadLockInfo* info = _ReadLockInfoAt(index)) { + // We already own a read lock. + if (fWriterCount > 0) { + // There are writers before us. + if (infiniteTimeout) { + // Timeout is infinite and there are writers before us. + // Unregister the read locks and lock as usual. + readerCount = info->count; + fWriterCount++; + fReaderCount -= readerCount; + _DeleteReadLockInfo(index); + error = B_OK; + } else { + // The timeout is finite and there are readers before us: + // let the write lock request fail. + error = B_WOULD_BLOCK; + } + } else if (info->count == fReaderCount) { + // No writers before us. + // We are the only read lock owners. Just move the read lock + // info data to the special writer fields and then we are done. + // Note: At this point we may overtake readers that already + // have acquired the queueing benaphore, but have not yet + // locked the data. But that doesn't harm. + fWriter = thread; + fWriterCount++; + fWriterWriterCount = 1; + fWriterReaderCount = info->count; + fReaderCount -= fWriterReaderCount; + _DeleteReadLockInfo(index); + locked = true; + error = B_OK; + } else { + // No writers before us, but other readers. + // Note, we're quite restrictive here. If there are only + // readers before us, we could reinstall our readers, if + // our request times out. Unfortunately it is not easy + // to ensure, that no writer overtakes us between unlocking + // the data and acquiring the queuing benaphore. + if (infiniteTimeout) { + // Unregister the readers and lock as usual. + readerCount = info->count; + fWriterCount++; + fReaderCount -= readerCount; + _DeleteReadLockInfo(index); + error = B_OK; + } else + error = B_WOULD_BLOCK; + } + } else { + // We don't own a read lock. + if (fWriter == thread) { + // ... but a write lock. + fWriterCount++; + fWriterWriterCount++; + locked = true; + error = B_OK; + } else { + // We own neither read nor write locks. + // Lock as usual. + fWriterCount++; + error = B_OK; + } + } + fLock.Unlock(); + // Usual locking... + // First step: acquire the queueing benaphore. + if (!locked && error == B_OK) { + error = _AcquireBenaphore(fQueue, timeout); + switch (error) { + case B_OK: + break; + case B_TIMED_OUT: { + // clean up + if (fLock.Lock()) { + fWriterCount--; + fLock.Unlock(); + } // else: failed to lock the data: we're probably going + // to die. + break; + } + default: + // Probably we're going to die. + break; + } + } + // Second step: acquire the mutex benaphore. + if (!locked && error == B_OK) { + error = _AcquireBenaphore(fMutex, timeout); + switch (error) { + case B_OK: { + // Yeah, we made it. Set the special writer fields. + fWriter = thread; + fWriterWriterCount = 1; + fWriterReaderCount = readerCount; + break; + } + case B_TIMED_OUT: { + // clean up + if (fLock.Lock()) { + fWriterCount--; + fLock.Unlock(); + } // else: failed to lock the data: we're probably going + // to die. + break; + } + default: + // Probably we're going to die. + break; + } + // Whatever happened, we have to release the queueing benaphore. + _ReleaseBenaphore(fQueue); + } + } else // failed to lock the data + error = B_ERROR; + return error; +} + +// _AddReadLockInfo +int32 +RWLocker::_AddReadLockInfo(ReadLockInfo* info) +{ + int32 index = fReadLockInfos.CountItems(); + fReadLockInfos.AddItem(info, index); + return index; +} + +// _NewReadLockInfo +// +// Create a new read lock info for the supplied thread and add it to the +// list. Returns the index of the info. +int32 +RWLocker::_NewReadLockInfo(thread_id thread, int32 count) +{ + ReadLockInfo* info = new ReadLockInfo; + info->reader = thread; + info->count = count; + return _AddReadLockInfo(info); +} + +// _DeleteReadLockInfo +void +RWLocker::_DeleteReadLockInfo(int32 index) +{ + if (ReadLockInfo* info = (ReadLockInfo*)fReadLockInfos.RemoveItem(index)) + delete info; +} + +// _ReadLockInfoAt +RWLocker::ReadLockInfo* +RWLocker::_ReadLockInfoAt(int32 index) const +{ + return (ReadLockInfo*)fReadLockInfos.ItemAt(index); +} + +// _IndexOf +int32 +RWLocker::_IndexOf(thread_id thread) const +{ + int32 count = fReadLockInfos.CountItems(); + for (int32 i = 0; i < count; i++) { + if (_ReadLockInfoAt(i)->reader == thread) + return i; + } + return -1; +} + +// _AcquireBenaphore +status_t +RWLocker::_AcquireBenaphore(Benaphore& benaphore, bigtime_t timeout) +{ + status_t error = B_OK; + if (atomic_add(&benaphore.counter, 1) > 0) { + error = acquire_sem_etc(benaphore.semaphore, 1, B_ABSOLUTE_TIMEOUT, + timeout); + } + return error; +} + +// _ReleaseBenaphore +void +RWLocker::_ReleaseBenaphore(Benaphore& benaphore) +{ + if (atomic_add(&benaphore.counter, -1) > 1) + release_sem(benaphore.semaphore); +} + diff --git a/src/tests/servers/app/newerClipping/RWLocker.h b/src/tests/servers/app/newerClipping/RWLocker.h new file mode 100644 index 0000000000..8c5bfc3ea7 --- /dev/null +++ b/src/tests/servers/app/newerClipping/RWLocker.h @@ -0,0 +1,126 @@ +// RWLocker.h +// +// This class provides a reader/writer locking mechanism: +// * A writer needs an exclusive lock. +// * For a reader a non-exclusive lock to be shared with other readers is +// sufficient. +// * The ownership of a lock is bound to the thread that requested the lock; +// the same thread has to call Unlock() later. +// * Nested locking is supported: a number of XXXLock() calls needs to be +// bracketed by the same number of XXXUnlock() calls. +// * The lock acquiration strategy is fair: a lock applicant needs to wait +// only for those threads that already own a lock or requested one before +// the current thread. No one can overtake. E.g. if a thread owns a read +// lock, another one is waiting for a write lock, then a third one +// requesting a read lock has to wait until the write locker is done. +// This does not hold for threads that already own a lock (nested locking). +// A read lock owner is immediately granted another read lock and a write +// lock owner another write or a read lock. +// * A write lock owner is allowed to request a read lock and a read lock +// owner a write lock. While the first case is not problematic, the +// second one needs some further explanation: A read lock owner requesting +// a write lock temporarily looses its read lock(s) until the write lock +// is granted. Otherwise two read lock owning threads trying to get +// write locks at the same time would dead lock each other. The only +// problem with this solution is, that the write lock acquiration must +// not fail, because in that case the thread could not be given back +// its read lock(s), since another thread may have been given a write lock +// in the mean time. Fortunately locking can fail only either, if the +// locker has been deleted, or, if a timeout occured. Therefore +// WriteLockWithTimeout() immediatlely returns with a B_WOULD_BLOCK error +// code, if the caller already owns a read lock (but no write lock) and +// another thread already owns or has requested a read or write lock. +// * Calls to read and write locking methods may interleave arbitrarily, +// e.g.: ReadLock(); WriteLock(); ReadUnlock(); WriteUnlock(); +// +// Important note: Read/WriteLock() can fail only, if the locker has been +// deleted. However, it is NOT save to invoke any method on a deleted +// locker object. +// +// Implementation details: +// A locker needs three semaphores (a BLocker and two semaphores): one +// to protect the lockers data, one as a reader/writer mutex (to be +// acquired by each writer and the first reader) and one for queueing +// waiting readers and writers. The simplified locking/unlocking +// algorithm is the following: +// +// writer reader +// queue.acquire() queue.acquire() +// mutex.acquire() if (first reader) mutex.acquire() +// queue.release() queue.release() +// ... ... +// mutex.release() if (last reader) mutex.release() +// +// One thread at maximum waits at the mutex, the others at the queueing +// semaphore. Unfortunately features as nested locking and timeouts make +// things more difficult. Therefore readers as well as writers need to check +// whether they already own a lock before acquiring the queueing semaphore. +// The data for the readers are stored in a list of ReadLockInfo structures; +// the writer data are stored in some special fields. /fReaderCount/ and +// /fWriterCount/ contain the total count of unbalanced Read/WriteLock() +// calls, /fWriterReaderCount/ and /fWriterWriterCount/ only from those of +// the current write lock owner (/fWriter/). To be a bit more precise: +// /fWriterReaderCount/ is not contained in /fReaderCount/, but +// /fWriterWriterCount/ is contained in /fWriterCount/. Therefore +// /fReaderCount/ can be considered to be the count of true reader's read +// locks. + +#ifndef RW_LOCKER_H +#define RW_LOCKER_H + +#include +#include + +class RWLocker { + public: + RWLocker(); + RWLocker(const char* name); + virtual ~RWLocker(); + + bool ReadLock(); + status_t ReadLockWithTimeout(bigtime_t timeout); + void ReadUnlock(); + bool IsReadLocked() const; + + bool WriteLock(); + status_t WriteLockWithTimeout(bigtime_t timeout); + void WriteUnlock(); + bool IsWriteLocked() const; + + private: + struct ReadLockInfo; + struct Benaphore { + sem_id semaphore; + int32 counter; + }; + + private: + void _Init(const char* name); + status_t _ReadLock(bigtime_t timeout); + status_t _WriteLock(bigtime_t timeout); + + int32 _AddReadLockInfo(ReadLockInfo* info); + int32 _NewReadLockInfo(thread_id thread, + int32 count = 1); + void _DeleteReadLockInfo(int32 index); + ReadLockInfo* _ReadLockInfoAt(int32 index) const; + int32 _IndexOf(thread_id thread) const; + + static status_t _AcquireBenaphore(Benaphore& benaphore, + bigtime_t timeout); + static void _ReleaseBenaphore(Benaphore& benaphore); + + private: + mutable BLocker fLock; // data lock + Benaphore fMutex; // critical code mutex + Benaphore fQueue; // queueing semaphore + int32 fReaderCount; // total count... + int32 fWriterCount; // total count... + BList fReadLockInfos; + thread_id fWriter; // current write lock owner + int32 fWriterWriterCount; // write lock owner count + int32 fWriterReaderCount; // writer read lock owner + // count +}; + +#endif // RW_LOCKER_H diff --git a/src/tests/servers/app/newerClipping/WindowLayer.cpp b/src/tests/servers/app/newerClipping/WindowLayer.cpp index 7c8bda7015..879b39bdf4 100644 --- a/src/tests/servers/app/newerClipping/WindowLayer.cpp +++ b/src/tests/servers/app/newerClipping/WindowLayer.cpp @@ -11,12 +11,21 @@ #include "WindowLayer.h" -#define SLOW_DRAWING 0 +// if the background clearing is delayed until +// the client draws the view, we have less flickering +// when contents have to be redrawn because of resizing +// a window or because the client invalidates parts. +// when redrawing something that has been exposed from underneath +// other windows, the other window will be seen longer at +// its previous position though if the exposed parts are not +// cleared right away. maybe there ought to be a flag in +// the update session, which tells us the cause of the update +#define DELAYED_BACKGROUND_CLEARING 1 // constructor WindowLayer::WindowLayer(BRect frame, const char* name, DrawingEngine* drawingEngine, Desktop* desktop) - : BLooper(name), + : BLooper(name, B_DISPLAY_PRIORITY), fFrame(frame), fVisibleRegion(), @@ -188,20 +197,19 @@ WindowLayer::GetContentRegion(BRegion* region) void WindowLayer::SetFocus(bool focus) { - if (Lock()) { - // executed from Desktop thread, so it's fine - // to use the clipping without locking - if (fDesktop->ReadLockClipping()) { - BRegion dirty(fBorderRegion); - dirty.IntersectWith(&fVisibleRegion); - fDesktop->MarkDirty(&dirty); + // executed from Desktop thread - fDesktop->ReadUnlockClipping(); - } + // since we don't mark parts dirty that + // don't intersect with our own visible + // region, readlocking is fine + if (fDesktop->ReadLockClipping()) { + BRegion dirty(fBorderRegion); + dirty.IntersectWith(&fVisibleRegion); + fDesktop->MarkDirty(&dirty); fFocus = focus; - Unlock(); + fDesktop->ReadUnlockClipping(); } } @@ -210,6 +218,7 @@ void WindowLayer::MoveBy(int32 x, int32 y) { // this function is only called from the desktop thread + // TODO: this fact needs review maybe if (x == 0 && y == 0) return; @@ -237,6 +246,9 @@ WindowLayer::MoveBy(int32 x, int32 y) void WindowLayer::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion) { + // this function is only called from the desktop thread + // TODO: this fact needs review maybe + if (x == 0 && y == 0) return; @@ -316,17 +328,17 @@ WindowLayer::MarkDirty(BRegion* regionOnScreen) void WindowLayer::MarkContentDirty(BRegion* regionOnScreen) { - // for triggering MSG_REDRAW +/* // for triggering MSG_REDRAW if (fDesktop && fDesktop->LockClipping()) { regionOnScreen->IntersectWith(&fVisibleContentRegion); fDesktop->MarkDirty(regionOnScreen); fDesktop->UnlockClipping(); - } + }*/ } -# pragma mark - +//# pragma mark - // CopyContents void @@ -338,28 +350,41 @@ WindowLayer::CopyContents(BRegion* region, int32 xOffset, int32 yOffset) BRegion newDirty(*region); + // clip the region to the visible contents at the + // source and destination location region->IntersectWith(&fVisibleContentRegion); - region->OffsetBy(xOffset, yOffset); - region->IntersectWith(&fVisibleContentRegion); + if (region->CountRects() > 0) { + region->OffsetBy(xOffset, yOffset); + region->IntersectWith(&fVisibleContentRegion); + if (region->CountRects() > 0) { + // if the region still contains any rects + // offset to source location again + region->OffsetBy(-xOffset, -yOffset); + // the part which we can copy is not dirty + newDirty.Exclude(region); + + if (fDrawingEngine->Lock()) { + fDrawingEngine->CopyRegion(region, xOffset, yOffset); + fDrawingEngine->Unlock(); + } - region->OffsetBy(-xOffset, -yOffset); - - newDirty.Exclude(region); - - _ShiftPartOfRegion(fDesktop->DirtyRegion(), region, xOffset, yOffset); - if (fDrawingEngine->Lock()) { - fDrawingEngine->CopyRegion(region, xOffset, yOffset); - fDrawingEngine->Unlock(); + // move along the already dirty regions that are common + // with the region that we could copy + _ShiftPartOfRegion(fDesktop->DirtyRegion(), region, xOffset, yOffset); + if (fCurrentUpdateSession) + _ShiftPartOfRegion(&fCurrentUpdateSession->DirtyRegion(), region, xOffset, yOffset); + if (fPendingUpdateSession) + _ShiftPartOfRegion(&fPendingUpdateSession->DirtyRegion(), region, xOffset, yOffset); + + } } - - if (fCurrentUpdateSession) - _ShiftPartOfRegion(&fCurrentUpdateSession->DirtyRegion(), region, xOffset, yOffset); - if (fPendingUpdateSession) - _ShiftPartOfRegion(&fPendingUpdateSession->DirtyRegion(), region, xOffset, yOffset); - + // what is left visible from the original region + // at the destination after the region which could be + // copied has been excluded, is considered dirty newDirty.OffsetBy(xOffset, yOffset); newDirty.IntersectWith(&fVisibleContentRegion); - fDesktop->MarkDirty(&newDirty); + if (newDirty.CountRects() > 0) + fDesktop->MarkDirty(&newDirty); fDesktop->UnlockClipping(); } @@ -372,11 +397,16 @@ void WindowLayer::_ShiftPartOfRegion(BRegion* region, BRegion* regionToShift, int32 xOffset, int32 yOffset) { - BRegion common(*region); - common.IntersectWith(regionToShift); - region->Exclude(&common); - common.OffsetBy(xOffset, yOffset); - region->Include(&common); + BRegion common(*regionToShift); + // see if there is a common part at all + common.IntersectWith(region); + if (common.CountRects() > 0) { + // cut the common part from the region, + // offset that to destination and include again + region->Exclude(&common); + common.OffsetBy(xOffset, yOffset); + region->Include(&common); + } } // _TriggerContentRedraw @@ -384,32 +414,32 @@ void WindowLayer::_TriggerContentRedraw() { //printf("%s - DrawContents()\n", Name()); - if (fDesktop->ReadLockClipping()) { - - BRegion dirtyContentRegion(fVisibleContentRegion); - dirtyContentRegion.IntersectWith(fDesktop->DirtyRegion()); + BRegion dirtyContentRegion(fVisibleContentRegion); + dirtyContentRegion.IntersectWith(fDesktop->DirtyRegion()); - if (dirtyContentRegion.CountRects() > 0) { + if (dirtyContentRegion.CountRects() > 0) { #if SHOW_WINDOW_CONTENT_DIRTY_REGION if (fDrawingEngine->Lock()) { - fDrawingEngine->SetHighColor(0, 0, 255); - fDrawingEngine->FillRegion(&dirtyContentRegion); - fDrawingEngine->MarkDirty(&dirtyContentRegion); - fDrawingEngine->Unlock(); - snooze(100000); +fDrawingEngine->SetHighColor(0, 0, 255); +fDrawingEngine->FillRegion(&dirtyContentRegion); +fDrawingEngine->MarkDirty(&dirtyContentRegion); +fDrawingEngine->Unlock(); +snooze(100000); } #endif - // send UPDATE message to the client - _MarkContentDirty(&dirtyContentRegion); + // send UPDATE message to the client + _MarkContentDirty(&dirtyContentRegion); - fDesktop->MarkClean(&dirtyContentRegion); + fDesktop->MarkClean(&dirtyContentRegion); - fTopLayer->Draw(fDrawingEngine, &dirtyContentRegion, - &fVisibleContentRegion, false); - } - - fDesktop->ReadUnlockClipping(); +#if DELAYED_BACKGROUND_CLEARING + fTopLayer->Draw(fDrawingEngine, &dirtyContentRegion, + &fVisibleContentRegion, false); +#else + fTopLayer->Draw(fDrawingEngine, &dirtyContentRegion, + &fVisibleContentRegion, true); +#endif } } @@ -417,6 +447,11 @@ if (fDrawingEngine->Lock()) { void WindowLayer::_DrawClient(int32 token) { + // This function is only executed in the window thread. + // It still needs to block on the clipping lock, since + // We have to be sure that the clipping is up to date. + // If true readlocking would work correctly, this would + // not be an issue ViewLayer* layer = (ViewLayer*)fTokenViewMap.ItemAt(token); if (!layer) return; @@ -434,15 +469,23 @@ WindowLayer::_DrawClient(int32 token) fEffectiveDrawingRegionValid = true; } + // TODO: this is a region that needs to be cached later in the server + // when the current layer in ServerWindow is set, and we are currently + // in an update (fInUpdate), than we can set this region and remember + // it for the comming drawing commands until the current layer changes + // again or fEffectiveDrawingRegionValid is suddenly false. BRegion effectiveClipping(fEffectiveDrawingRegion); effectiveClipping.IntersectWith(&layer->ScreenClipping(&fVisibleContentRegion)); if (effectiveClipping.CountRects() > 0) { +#if DELAYED_BACKGROUND_CLEARING // clear the back ground // TODO: only if this is the first drawing command for - // this layer of course! + // this layer of course! in the simulation, all client + // drawing is done from a single command yet layer->Draw(fDrawingEngine, &effectiveClipping, &fVisibleContentRegion, false); +#endif layer->ClientDraw(fDrawingEngine, &effectiveClipping); } @@ -455,53 +498,86 @@ WindowLayer::_DrawClient(int32 token) void WindowLayer::_DrawBorder() { -//printf("%s - DrawBorder()\n", Name()); -#if SLOW_DRAWING - snooze(10000); -#endif - if (fDesktop->ReadLockClipping()) { - - // construct the region of the border that needs redrawing - BRegion dirtyBorderRegion; - GetBorderRegion(&dirtyBorderRegion); - // intersect with our visible region - dirtyBorderRegion.IntersectWith(&fVisibleRegion); - // intersect with the Desktop's dirty region - dirtyBorderRegion.IntersectWith(fDesktop->DirtyRegion()); - - if (dirtyBorderRegion.CountRects() > 0) { - if (fDrawingEngine->Lock()) { + // this is executed in the window thread, but only + // in respond to MSG_REDRAW having been received, the + // clipping lock is held for reading - fDrawingEngine->ConstrainClippingRegion(&dirtyBorderRegion); + // construct the region of the border that needs redrawing + BRegion dirtyBorderRegion; + GetBorderRegion(&dirtyBorderRegion); + // intersect with our visible region + dirtyBorderRegion.IntersectWith(&fVisibleRegion); + // intersect with the Desktop's dirty region + dirtyBorderRegion.IntersectWith(fDesktop->DirtyRegion()); - if (fFocus) { - fDrawingEngine->SetLowColor(255, 203, 0, 255); - fDrawingEngine->SetHighColor(0, 0, 0, 255); - } else { - fDrawingEngine->SetLowColor(216, 216, 216, 0); - fDrawingEngine->SetHighColor(0, 0, 0, 255); - } + if (dirtyBorderRegion.CountRects() > 0) { + if (fDrawingEngine->Lock()) { - fDrawingEngine->FillRect(dirtyBorderRegion.Frame(), B_SOLID_LOW); - fDrawingEngine->DrawString(Name(), BPoint(fFrame.left, fFrame.top - 5)); + fDrawingEngine->ConstrainClippingRegion(&dirtyBorderRegion); - fDrawingEngine->ConstrainClippingRegion(NULL); - fDrawingEngine->MarkDirty(&dirtyBorderRegion); - fDrawingEngine->Unlock(); + if (fFocus) { + fDrawingEngine->SetLowColor(255, 203, 0, 255); + fDrawingEngine->SetHighColor(0, 0, 0, 255); + } else { + fDrawingEngine->SetLowColor(216, 216, 216, 0); + fDrawingEngine->SetHighColor(30, 30, 30, 255); } - fDesktop->MarkClean(&dirtyBorderRegion); + rgb_color light = tint_color(fDrawingEngine->LowColor(), B_LIGHTEN_2_TINT); + rgb_color shadow = tint_color(fDrawingEngine->LowColor(), B_DARKEN_2_TINT); + + fDrawingEngine->FillRect(dirtyBorderRegion.Frame(), B_SOLID_LOW); + + fDrawingEngine->DrawString(Name(), BPoint(fFrame.left, fFrame.top - 5)); + + BRect frame(fFrame); + fDrawingEngine->BeginLineArray(12); + frame.InsetBy(-1, -1); + fDrawingEngine->AddLine(BPoint(frame.left, frame.bottom), + BPoint(frame.left, frame.top), shadow); + fDrawingEngine->AddLine(BPoint(frame.left + 1, frame.top), + BPoint(frame.right, frame.top), shadow); + fDrawingEngine->AddLine(BPoint(frame.right, frame.top + 1), + BPoint(frame.right, frame.bottom - 11), light); + fDrawingEngine->AddLine(BPoint(frame.right - 1, frame.bottom - 11), + BPoint(frame.right - 11, frame.bottom - 11), light); + fDrawingEngine->AddLine(BPoint(frame.right - 11, frame.bottom - 10), + BPoint(frame.right - 11, frame.bottom), light); + fDrawingEngine->AddLine(BPoint(frame.right - 12, frame.bottom), + BPoint(frame.left + 1, frame.bottom), light); + + frame.InsetBy(-3, -3); + fDrawingEngine->AddLine(BPoint(frame.left, frame.bottom), + BPoint(frame.left, frame.top - 16), light); + fDrawingEngine->AddLine(BPoint(frame.left + 1, frame.top - 16), + BPoint((frame.left + frame.right) / 2, frame.top - 16), light); + fDrawingEngine->AddLine(BPoint((frame.left + frame.right) / 2, frame.top - 15), + BPoint((frame.left + frame.right) / 2, frame.top), shadow); + fDrawingEngine->AddLine(BPoint((frame.left + frame.right) / 2 + 1, frame.top), + BPoint(frame.left + frame.right, frame.top), light); + fDrawingEngine->AddLine(BPoint(frame.right, frame.top + 1), + BPoint(frame.right, frame.bottom), shadow); + fDrawingEngine->AddLine(BPoint(frame.right, frame.bottom), + BPoint(frame.left + 1, frame.bottom), shadow); + fDrawingEngine->EndLineArray(); + + fDrawingEngine->ConstrainClippingRegion(NULL); + fDrawingEngine->MarkDirty(&dirtyBorderRegion); + fDrawingEngine->Unlock(); } - fDesktop->ReadUnlockClipping(); + fDesktop->MarkClean(&dirtyBorderRegion); } } // _MarkContentDirty +// +// pre: the clipping is readlocked (this function is +// only called from _TriggerContentRedraw()), which +// in turn is only called from MessageReceived() with +// the clipping lock held void WindowLayer::_MarkContentDirty(BRegion* contentDirtyRegion) { -if (fDesktop->ReadLockClipping()) { - if (contentDirtyRegion->CountRects() <= 0) return; @@ -513,9 +589,12 @@ if (fDesktop->ReadLockClipping()) { fPendingUpdateSession->Include(contentDirtyRegion); } - // clip pending update session from current, - // current update session, because the backgrounds have been - // cleared again already + // clip pending update session from current + // update session, it makes no sense to draw stuff + // already needing a redraw anyways. Theoretically, + // this could be done smarter (clip layers from pending + // that have not yet been redrawn in the current update + // session) if (fCurrentUpdateSession) { fCurrentUpdateSession->Exclude(contentDirtyRegion); fEffectiveDrawingRegionValid = false; @@ -525,58 +604,67 @@ if (fDesktop->ReadLockClipping()) { // send this to client fClient->PostMessage(MSG_UPDATE); fUpdateRequested = true; + // as long as we have not received + // the "begin update" command, the + // pending session does not become the + // current } - -fDesktop->ReadUnlockClipping(); -} } // _BeginUpdate void WindowLayer::_BeginUpdate() { -if (fDesktop->ReadLockClipping()) { + // TODO: since we might "shift" parts of the + // internal dirty regions from the desktop thread + // in respond to WindowLayer::ResizeBy(), which + // might move arround views, this function needs to block + // on the global clipping lock so that the internal + // dirty regions are not messed with from both threads + // at the same time. + if (fDesktop->ReadLockClipping()) { - if (fUpdateRequested && !fCurrentUpdateSession) { - fCurrentUpdateSession = fPendingUpdateSession; - fPendingUpdateSession = NULL; - - if (fCurrentUpdateSession) { - // all drawing command from the client - // will have the dirty region from the update - // session enforced - fInUpdate = true; + if (fUpdateRequested && !fCurrentUpdateSession) { + fCurrentUpdateSession = fPendingUpdateSession; + fPendingUpdateSession = NULL; + + if (fCurrentUpdateSession) { + // all drawing command from the client + // will have the dirty region from the update + // session enforced + fInUpdate = true; + } + fEffectiveDrawingRegionValid = false; } - fEffectiveDrawingRegionValid = false; - } -fDesktop->ReadUnlockClipping(); -} + fDesktop->ReadUnlockClipping(); + } } // _EndUpdate void WindowLayer::_EndUpdate() { -if (fDesktop->ReadLockClipping()) { - - if (fInUpdate) { - delete fCurrentUpdateSession; - fCurrentUpdateSession = NULL; + // TODO: see comment in _BeginUpdate() + if (fDesktop->ReadLockClipping()) { - fInUpdate = false; - fEffectiveDrawingRegionValid = false; + if (fInUpdate) { + delete fCurrentUpdateSession; + fCurrentUpdateSession = NULL; + + fInUpdate = false; + fEffectiveDrawingRegionValid = false; + } + if (fPendingUpdateSession) { + // send this to client + fClient->PostMessage(MSG_UPDATE); + fUpdateRequested = true; + } else { + fUpdateRequested = false; + } + + fDesktop->ReadUnlockClipping(); } - if (fPendingUpdateSession) { - // send this to client - fClient->PostMessage(MSG_UPDATE); - fUpdateRequested = true; - } else { - fUpdateRequested = false; - } - -fDesktop->ReadUnlockClipping(); -} } #pragma mark - diff --git a/src/tests/servers/app/newerClipping/main.cpp b/src/tests/servers/app/newerClipping/main.cpp index be19c388c6..cb6221bbc2 100644 --- a/src/tests/servers/app/newerClipping/main.cpp +++ b/src/tests/servers/app/newerClipping/main.cpp @@ -172,6 +172,8 @@ void Window::Test() { BRect frame(20, 20, 330, 230); +// AddWindow(frame, "Window 1"); +// AddWindow(frame, "Window 2"); for (int32 i = 0; i < 20; i++) { BString name("Window "); frame.OffsetBy(20, 15); @@ -179,7 +181,7 @@ Window::Test() AddWindow(frame, name.String()); } - frame.Set(10, 80, 320, 290); +/* frame.Set(10, 80, 320, 290); for (int32 i = 20; i < 40; i++) { BString name("Window "); frame.OffsetBy(20, 15); @@ -187,7 +189,7 @@ Window::Test() AddWindow(frame, name.String()); } -/* frame.Set(20, 140, 330, 230); + frame.Set(20, 140, 330, 230); for (int32 i = 40; i < 60; i++) { BString name("Window "); frame.OffsetBy(20, 15); diff --git a/src/tests/servers/app/newerClipping/makefile b/src/tests/servers/app/newerClipping/makefile index a8b13b4f8a..05e67817c7 100644 --- a/src/tests/servers/app/newerClipping/makefile +++ b/src/tests/servers/app/newerClipping/makefile @@ -35,6 +35,7 @@ SRCS= ClientLooper.cpp \ DrawingEngine.cpp \ main.cpp \ MultiLocker.cpp \ + RWLocker.cpp \ ViewLayer.cpp \ WindowLayer.cpp