* added a multi locker implementation from Ingo Weinhold,

which supports ReadLockWithTimeout()
* commented the code in many more places
* understood the problem of making the read/write locking
  work: While it would be possible for each window to
  remove the processed region from the global dirty region
  in read lock mode (since it is guaranteed not remove
  a region not intersecting with its own visible region),
  multiple window threads can still not do this at the same
  time, since BRegion itself is not threadsafe of course.
* I need to figure out a way for the window threads to be
  able to access and modify all needed data in read only mode,
  I think this means to divide the global dirty region into
  each window again, so that each window thread can simply
  clear its own dirty region instead of excluding it from
  the global region. Yeah, that might work.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@15230 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus 2005-11-29 19:08:58 +00:00
parent a9b83b752c
commit 4dd89c6903
8 changed files with 857 additions and 143 deletions

View File

@ -13,7 +13,7 @@
// constructor
ClientLooper::ClientLooper(const char* name, WindowLayer* serverWindow)
: BLooper(""),
: BLooper("", B_DISPLAY_PRIORITY),
fServerWindow(serverWindow),
fViewCount(0)
{

View File

@ -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);

View File

@ -3,24 +3,34 @@
#define DESKTOP_H
#include <List.h>
#include <Locker.h>
#include <Region.h>
#include <View.h>
#include <Window.h>
#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 <Locker.h>
#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

View File

@ -0,0 +1,465 @@
// RWLocker.cpp
#include <String.h>
#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);
}

View File

@ -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 <List.h>
#include <Locker.h>
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

View File

@ -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 -

View File

@ -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);

View File

@ -35,6 +35,7 @@ SRCS= ClientLooper.cpp \
DrawingEngine.cpp \
main.cpp \
MultiLocker.cpp \
RWLocker.cpp \
ViewLayer.cpp \
WindowLayer.cpp