Backed out 8 changesets c8de1f3f0bf3, fc0ab17babda, 051166e3d6e4, 2c11c33beb7c, f52ff32dfb47, 9fe51adf6a70, 6ad2ac42107c, 022810f9a65a (bug 1083101) for for frequent gtest timeouts in Moz2D.TaskScheduler_Join. r=backout on a CLOSED TREE

This commit is contained in:
Sebastian Hengst 2015-09-04 20:03:45 +02:00
parent 5acea77a37
commit 1eefe94873
17 changed files with 24 additions and 2194 deletions

View File

@ -1,80 +0,0 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZILLA_GFX_CRITICALSECTION_H_
#define MOZILLA_GFX_CRITICALSECTION_H_
#ifdef WIN32
#include <windows.h>
#else
#include <pthread.h>
#include "mozilla/DebugOnly.h"
#endif
namespace mozilla {
namespace gfx {
#ifdef WIN32
class CriticalSection {
public:
CriticalSection() { ::InitializeCriticalSection(&mCriticalSection); }
~CriticalSection() { ::DeleteCriticalSection(&mCriticalSection); }
void Enter() { ::EnterCriticalSection(&mCriticalSection); }
void Leave() { ::LeaveCriticalSection(&mCriticalSection); }
protected:
CRITICAL_SECTION mCriticalSection;
};
#else
// posix
class PosixCondvar;
class CriticalSection {
public:
CriticalSection() {
DebugOnly<int> err = pthread_mutex_init(&mMutex, nullptr);
MOZ_ASSERT(!err);
}
~CriticalSection() {
DebugOnly<int> err = pthread_mutex_destroy(&mMutex);
MOZ_ASSERT(!err);
}
void Enter() {
DebugOnly<int> err = pthread_mutex_lock(&mMutex);
MOZ_ASSERT(!err);
}
void Leave() {
DebugOnly<int> err = pthread_mutex_unlock(&mMutex);
MOZ_ASSERT(!err);
}
protected:
pthread_mutex_t mMutex;
friend class PosixCondVar;
};
#endif
/// RAII helper.
struct CriticalSectionAutoEnter {
explicit CriticalSectionAutoEnter(CriticalSection* aSection) : mSection(aSection) { mSection->Enter(); }
~CriticalSectionAutoEnter() { mSection->Leave(); }
protected:
CriticalSection* mSection;
};
} // namespace
} // namespace
#endif

View File

@ -6,10 +6,6 @@
#ifndef MOZILLA_GFX_DRAWCOMMAND_H_
#define MOZILLA_GFX_DRAWCOMMAND_H_
#define _USE_MATH_DEFINES
#include <math.h>
#include "2D.h"
#include "Filters.h"
#include <vector>
@ -35,8 +31,7 @@ enum class CommandType : int8_t {
PUSHCLIP,
PUSHCLIPRECT,
POPCLIP,
SETTRANSFORM,
FLUSH
SETTRANSFORM
};
class DrawingCommand
@ -44,9 +39,7 @@ class DrawingCommand
public:
virtual ~DrawingCommand() {}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform = nullptr) const = 0;
virtual bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const { return false; }
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aTransform) = 0;
protected:
explicit DrawingCommand(CommandType aType)
@ -137,7 +130,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->DrawSurface(mSurface, mDest, mSource, mSurfOptions, mOptions);
}
@ -161,7 +154,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->DrawFilter(mFilter, mSourceRect, mDestPoint, mOptions);
}
@ -182,7 +175,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->ClearRect(mRect);
}
@ -204,13 +197,11 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aTransform)
{
MOZ_ASSERT(!aTransform || !aTransform->HasNonIntegerTranslation());
MOZ_ASSERT(!aTransform.HasNonIntegerTranslation());
Point dest(Float(mDestination.x), Float(mDestination.y));
if (aTransform) {
dest = (*aTransform) * dest;
}
dest = aTransform * dest;
aDT->CopySurface(mSurface, mSourceRect, IntPoint(uint32_t(dest.x), uint32_t(dest.y)));
}
@ -233,17 +224,11 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->FillRect(mRect, mPattern, mOptions);
}
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
{
aDeviceRect = aTransform.TransformBounds(mRect);
return true;
}
private:
Rect mRect;
StoredPattern mPattern;
@ -263,14 +248,9 @@ public:
, mStrokeOptions(aStrokeOptions)
, mOptions(aOptions)
{
if (aStrokeOptions.mDashLength) {
mDashes.resize(aStrokeOptions.mDashLength);
mStrokeOptions.mDashPattern = &mDashes.front();
memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float));
}
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->StrokeRect(mRect, mPattern, mStrokeOptions, mOptions);
}
@ -280,7 +260,6 @@ private:
StoredPattern mPattern;
StrokeOptions mStrokeOptions;
DrawOptions mOptions;
std::vector<Float> mDashes;
};
class StrokeLineCommand : public DrawingCommand
@ -300,7 +279,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->StrokeLine(mStart, mEnd, mPattern, mStrokeOptions, mOptions);
}
@ -326,58 +305,17 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->Fill(mPath, mPattern, mOptions);
}
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
{
aDeviceRect = mPath->GetBounds(aTransform);
return true;
}
private:
RefPtr<Path> mPath;
StoredPattern mPattern;
DrawOptions mOptions;
};
#ifndef M_SQRT2
#define M_SQRT2 1.41421356237309504880
#endif
#ifndef M_SQRT1_2
#define M_SQRT1_2 0.707106781186547524400844362104849039
#endif
// The logic for this comes from _cairo_stroke_style_max_distance_from_path
static Rect
PathExtentsToMaxStrokeExtents(const StrokeOptions &aStrokeOptions,
const Rect &aRect,
const Matrix &aTransform)
{
double styleExpansionFactor = 0.5f;
if (aStrokeOptions.mLineCap == CapStyle::SQUARE) {
styleExpansionFactor = M_SQRT1_2;
}
if (aStrokeOptions.mLineJoin == JoinStyle::MITER &&
styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) {
styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit;
}
styleExpansionFactor *= aStrokeOptions.mLineWidth;
double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21);
double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12);
Rect result = aRect;
result.Inflate(dx, dy);
return result;
}
class StrokeCommand : public DrawingCommand
{
public:
@ -391,30 +329,18 @@ public:
, mStrokeOptions(aStrokeOptions)
, mOptions(aOptions)
{
if (aStrokeOptions.mDashLength) {
mDashes.resize(aStrokeOptions.mDashLength);
mStrokeOptions.mDashPattern = &mDashes.front();
memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float));
}
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->Stroke(mPath, mPattern, mStrokeOptions, mOptions);
}
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
{
aDeviceRect = PathExtentsToMaxStrokeExtents(mStrokeOptions, mPath->GetBounds(aTransform), aTransform);
return true;
}
private:
RefPtr<Path> mPath;
StoredPattern mPattern;
StrokeOptions mStrokeOptions;
DrawOptions mOptions;
std::vector<Float> mDashes;
};
class FillGlyphsCommand : public DrawingCommand
@ -435,7 +361,7 @@ public:
memcpy(&mGlyphs.front(), aBuffer.mGlyphs, sizeof(Glyph) * aBuffer.mNumGlyphs);
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
GlyphBuffer buf;
buf.mNumGlyphs = mGlyphs.size();
@ -464,7 +390,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->Mask(mSource, mMask, mOptions);
}
@ -490,7 +416,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->MaskSurface(mSource, mMask, mOffset, mOptions);
}
@ -511,7 +437,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->PushClip(mPath);
}
@ -529,7 +455,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->PushClipRect(mRect);
}
@ -546,7 +472,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
{
aDT->PopClip();
}
@ -561,33 +487,17 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aMatrix) const
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aMatrix)
{
if (aMatrix) {
aDT->SetTransform(mTransform * (*aMatrix));
} else {
aDT->SetTransform(mTransform);
}
Matrix transform = mTransform;
transform *= aMatrix;
aDT->SetTransform(transform);
}
private:
Matrix mTransform;
};
class FlushCommand : public DrawingCommand
{
public:
explicit FlushCommand()
: DrawingCommand(CommandType::FLUSH)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->Flush();
}
};
} // namespace gfx
} // namespace mozilla

View File

@ -188,7 +188,7 @@ DrawTargetCaptureImpl::ReplayToDrawTarget(DrawTarget* aDT, const Matrix& aTransf
uint8_t* current = start;
while (current < start + mDrawCommandStorage.size()) {
reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->ExecuteOnDT(aDT, &aTransform);
reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->ExecuteOnDT(aDT, aTransform);
current += *(uint32_t*)current;
}
}

View File

@ -1,116 +0,0 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DrawingTask.h"
#include "TaskScheduler.h"
#include "mozilla/gfx/2D.h"
namespace mozilla {
namespace gfx {
DrawingTaskBuilder::DrawingTaskBuilder()
: mTask(nullptr)
{}
DrawingTaskBuilder::~DrawingTaskBuilder()
{
if (mTask) {
delete mTask;
}
}
void
DrawingTask::Clear()
{
mCommandBuffer = nullptr;
mCursor = 0;
}
void
DrawingTaskBuilder::BeginDrawingTask(MultiThreadedTaskQueue* aTaskQueue,
DrawTarget* aTarget, IntPoint aOffset,
SyncObject* aStart)
{
MOZ_ASSERT(!mTask);
MOZ_ASSERT(aTaskQueue);
mTask = new DrawingTask(aTaskQueue, aTarget, aOffset, aStart);
}
DrawingTask*
DrawingTaskBuilder::EndDrawingTask(CommandBuffer* aCmdBuffer, SyncObject* aCompletion)
{
MOZ_ASSERT(mTask);
mTask->mCompletionSync = aCompletion;
mTask->mCommandBuffer = aCmdBuffer;
DrawingTask* task = mTask;
mTask = nullptr;
return task;
}
DrawingTask::DrawingTask(MultiThreadedTaskQueue* aTaskQueue,
DrawTarget* aTarget, IntPoint aOffset,
SyncObject* aStart)
: Task(aTaskQueue, aStart, nullptr)
, mCommandBuffer(nullptr)
, mCursor(0)
, mDrawTarget(aTarget)
, mOffset(aOffset)
{
mCommandOffsets.reserve(64);
}
TaskStatus
DrawingTask::Run()
{
while (mCursor < mCommandOffsets.size()) {
const DrawingCommand* cmd = mCommandBuffer->GetDrawingCommand(mCommandOffsets[mCursor]);
if (!cmd) {
return TaskStatus::Error;
}
cmd->ExecuteOnDT(mDrawTarget);
++mCursor;
}
return TaskStatus::Complete;
}
DrawingTask::~DrawingTask()
{
Clear();
}
const DrawingCommand*
CommandBuffer::GetDrawingCommand(ptrdiff_t aId)
{
return static_cast<DrawingCommand*>(mStorage.GetStorage(aId));
}
CommandBuffer::~CommandBuffer()
{
mStorage.ForEach([](void* item){
static_cast<DrawingCommand*>(item)->~DrawingCommand();
});
mStorage.Clear();
}
void
CommandBufferBuilder::BeginCommandBuffer(size_t aBufferSize)
{
MOZ_ASSERT(!mCommands);
mCommands = new CommandBuffer(aBufferSize);
}
already_AddRefed<CommandBuffer>
CommandBufferBuilder::EndCommandBuffer()
{
return mCommands.forget();
}
} // namespace
} // namespace

View File

@ -1,153 +0,0 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZILLA_GFX_COMMANDBUFFER_H_
#define MOZILLA_GFX_COMMANDBUFFER_H_
#include <stdint.h>
#include "mozilla/RefPtr.h"
#include "mozilla/Assertions.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/gfx/TaskScheduler.h"
#include "mozilla/gfx/IterableArena.h"
#include "DrawCommand.h"
namespace mozilla {
namespace gfx {
class DrawingCommand;
class PrintCommand;
class SignalCommand;
class DrawingTask;
class WaitCommand;
class SyncObject;
class MultiThreadedTaskQueue;
class DrawTarget;
class DrawingTaskBuilder;
class CommandBufferBuilder;
/// Contains a sequence of immutable drawing commands that are typically used by
/// several DrawingTasks.
///
/// CommandBuffer objects are built using CommandBufferBuilder.
class CommandBuffer : public external::AtomicRefCounted<CommandBuffer>
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(CommandBuffer)
~CommandBuffer();
const DrawingCommand* GetDrawingCommand(ptrdiff_t aId);
protected:
explicit CommandBuffer(size_t aSize = 256)
: mStorage(IterableArena::GROWABLE, aSize)
{}
IterableArena mStorage;
friend class CommandBufferBuilder;
};
/// Generates CommandBuffer objects.
///
/// The builder is a separate object to ensure that commands are not added to a
/// submitted CommandBuffer.
class CommandBufferBuilder
{
public:
void BeginCommandBuffer(size_t aBufferSize = 256);
already_AddRefed<CommandBuffer> EndCommandBuffer();
/// Build the CommandBuffer, command after command.
/// This must be used between BeginCommandBuffer and EndCommandBuffer.
template<typename T, typename... Args>
ptrdiff_t AddCommand(Args&&... aArgs)
{
static_assert(IsBaseOf<DrawingCommand, T>::value,
"T must derive from DrawingCommand");
return mCommands->mStorage.Alloc<T>(Forward<Args>(aArgs)...);
}
bool HasCommands() const { return !!mCommands; }
protected:
RefPtr<CommandBuffer> mCommands;
};
/// Stores multiple commands to be executed sequencially.
class DrawingTask : public Task {
public:
DrawingTask(MultiThreadedTaskQueue* aTaskQueue,
DrawTarget* aTarget,
IntPoint aOffset,
SyncObject* aStart);
~DrawingTask();
virtual TaskStatus Run() override;
protected:
/// Runs the tasks's destructors and resets the buffer.
void Clear();
std::vector<ptrdiff_t> mCommandOffsets;
RefPtr<CommandBuffer> mCommandBuffer;
uint32_t mCursor;
RefPtr<DrawTarget> mDrawTarget;
IntPoint mOffset;
friend class DrawingTaskBuilder;
};
/// Generates DrawingTask objects.
///
/// The builder is a separate object to ensure that commands are not added to a
/// submitted DrawingTask.
class DrawingTaskBuilder {
public:
DrawingTaskBuilder();
~DrawingTaskBuilder();
/// Allocates a DrawingTask.
///
/// call this method before starting to add commands.
void BeginDrawingTask(MultiThreadedTaskQueue* aTaskQueue,
DrawTarget* aTarget, IntPoint aOffset,
SyncObject* aStart = nullptr);
/// Build the DrawingTask, command after command.
/// This must be used between BeginDrawingTask and EndDrawingTask.
void AddCommand(ptrdiff_t offset)
{
MOZ_ASSERT(mTask);
mTask->mCommandOffsets.push_back(offset);
}
/// Finalizes and returns the command buffer.
///
/// If aCompletion is not null, the sync object will be signaled after the
/// task buffer is destroyed (and after the destructor of the tasks have run).
/// In most cases this means after the completion of all tasks in the task buffer,
/// but also when the task buffer is destroyed due to an error.
DrawingTask* EndDrawingTask(CommandBuffer* aCmdBuffer, SyncObject* aCompletion = nullptr);
/// Returns true between BeginDrawingTask and EndDrawingTask, false otherwise.
bool HasDrawingTask() const { return !!mTask; }
protected:
DrawingTask* mTask;
};
} // namespace
} // namespace
#endif

View File

@ -1,193 +0,0 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZILLA_GFX_ITERABLEARENA_H_
#define MOZILLA_GFX_ITERABLEARENA_H_
#include "mozilla/Move.h"
#include "mozilla/Assertions.h"
#include "mozilla/gfx/Logging.h"
#include <string.h>
#include <vector>
#include <stdint.h>
#include <stdio.h>
namespace mozilla {
namespace gfx {
/// A simple pool allocator for plain data structures.
///
/// Beware that the pool will not attempt to run the destructors. It is the
/// responsibility of the user of this class to either use objects with no
/// destructor or to manually call the allocated objects destructors.
/// If the pool is growable, its allocated objects must be safely moveable in
/// in memory (through memcpy).
class IterableArena {
protected:
struct Header
{
size_t mBlocSize;
};
public:
enum ArenaType {
FIXED_SIZE,
GROWABLE
};
IterableArena(ArenaType aType, size_t aStorageSize)
: mSize(aStorageSize)
, mCursor(0)
, mIsGrowable(aType == GROWABLE)
{
if (mSize == 0) {
mSize = 128;
}
mStorage = (uint8_t*)malloc(mSize);
if (mStorage == nullptr) {
gfxCriticalError() << "Not enough Memory allocate a memory pool of size " << aStorageSize;
MOZ_CRASH();
}
}
~IterableArena()
{
free(mStorage);
}
/// Constructs a new item in the pool and returns a positive offset in case of
/// success.
///
/// The offset never changes even if the storage is reallocated, so users
/// of this class should prefer storing offsets rather than direct pointers
/// to the allocated objects.
/// Alloc can cause the storage to be reallocated if the pool was initialized
/// with IterableArena::GROWABLE.
/// If for any reason the pool fails to allocate enough space for the new item
/// Alloc returns a negative offset and the object's constructor is not called.
template<typename T, typename... Args>
ptrdiff_t
Alloc(Args&&... aArgs)
{
void* storage = nullptr;
auto offset = AllocRaw(sizeof(T), &storage);
if (offset < 0) {
return offset;
}
new (storage) T(Forward<Args>(aArgs)...);
return offset;
}
ptrdiff_t AllocRaw(size_t aSize, void** aOutPtr = nullptr)
{
const size_t blocSize = AlignedSize(sizeof(Header) + aSize);
if (AlignedSize(mCursor + blocSize) > mSize) {
if (!mIsGrowable) {
return -1;
}
size_t newSize = mSize * 2;
while (AlignedSize(mCursor + blocSize) > newSize) {
newSize *= 2;
}
uint8_t* newStorage = (uint8_t*)realloc(mStorage, newSize);
if (!newStorage) {
gfxCriticalError() << "Not enough Memory to grow the memory pool, size: " << newSize;
return -1;
}
mStorage = newStorage;
mSize = newSize;
}
ptrdiff_t offset = mCursor;
GetHeader(offset)->mBlocSize = blocSize;
mCursor += blocSize;
if (aOutPtr) {
*aOutPtr = GetStorage(offset);
}
return offset;
}
/// Get access to an allocated item at a given offset (only use offsets returned
/// by Alloc or AllocRaw).
///
/// If the pool is growable, the returned pointer is only valid temporarily. The
/// underlying storage can be reallocated in Alloc or AllocRaw, so do not keep
/// these pointers around and store the offset instead.
void* GetStorage(ptrdiff_t offset = 0)
{
MOZ_ASSERT(offset >= 0);
MOZ_ASSERT(offset < mCursor);
return offset >= 0 ? mStorage + offset + sizeof(Header) : nullptr;
}
/// Clears the storage without running any destructor and without deallocating it.
void Clear()
{
mCursor = 0;
}
/// Iterate over the elements allocated in this pool.
///
/// Takes a lambda or function object accepting a void* as parameter.
template<typename Func>
void ForEach(Func cb)
{
Iterator it;
while (void* ptr = it.Next(this)) {
cb(ptr);
}
}
/// A simple iterator over an arena.
class Iterator {
public:
Iterator()
: mCursor(0)
{}
void* Next(IterableArena* aArena)
{
if (mCursor >= aArena->mCursor) {
return nullptr;
}
void* result = aArena->GetStorage(mCursor);
const size_t blocSize = aArena->GetHeader(mCursor)->mBlocSize;
MOZ_ASSERT(blocSize != 0);
mCursor += blocSize;
return result;
}
private:
ptrdiff_t mCursor;
};
protected:
Header* GetHeader(ptrdiff_t offset)
{
return (Header*) (mStorage + offset);
}
size_t AlignedSize(size_t aSize) const
{
const size_t alignment = sizeof(uintptr_t);
return aSize + (alignment - (aSize % alignment)) % alignment;
}
uint8_t* mStorage;
uint32_t mSize;
ptrdiff_t mCursor;
bool mIsGrowable;
friend class Iterator;
};
} // namespace
} // namespace
#endif

View File

@ -1,260 +0,0 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TaskScheduler.h"
namespace mozilla {
namespace gfx {
TaskScheduler* TaskScheduler::sSingleton = nullptr;
bool TaskScheduler::Init(uint32_t aNumThreads, uint32_t aNumQueues)
{
MOZ_ASSERT(!sSingleton);
MOZ_ASSERT(aNumThreads >= aNumQueues);
sSingleton = new TaskScheduler();
sSingleton->mNextQueue = 0;
for (uint32_t i = 0; i < aNumQueues; ++i) {
sSingleton->mDrawingQueues.push_back(new MultiThreadedTaskQueue());
}
for (uint32_t i = 0; i < aNumThreads; ++i) {
sSingleton->mWorkerThreads.push_back(WorkerThread::Create(sSingleton->mDrawingQueues[i%aNumQueues]));
}
return true;
}
void TaskScheduler::ShutDown()
{
MOZ_ASSERT(IsEnabled());
if (!IsEnabled()) {
return;
}
for (auto queue : sSingleton->mDrawingQueues) {
queue->ShutDown();
delete queue;
}
for (WorkerThread* thread : sSingleton->mWorkerThreads) {
// this will block until the thread is joined.
delete thread;
}
sSingleton->mWorkerThreads.clear();
delete sSingleton;
sSingleton = nullptr;
}
TaskStatus
TaskScheduler::ProcessTask(Task* aTask)
{
MOZ_ASSERT(aTask);
auto status = aTask->Run();
if (status == TaskStatus::Error || status == TaskStatus::Complete) {
delete aTask;
}
return status;
}
void
TaskScheduler::SubmitTask(Task* aTask)
{
MOZ_ASSERT(aTask);
RefPtr<SyncObject> start = aTask->GetStartSync();
if (start && start->Register(aTask)) {
// The Task buffer starts with a non-signaled sync object, it
// is now registered in the list of task buffers waiting on the
// sync object, so we should not place it in the queue.
return;
}
aTask->GetTaskQueue()->SubmitTask(aTask);
}
Task::Task(MultiThreadedTaskQueue* aQueue, SyncObject* aStart, SyncObject* aCompletion)
: mQueue(aQueue)
, mStartSync(aStart)
, mCompletionSync(aCompletion)
{
if (mStartSync) {
mStartSync->AddSubsequent(this);
}
if (mCompletionSync) {
mCompletionSync->AddPrerequisite(this);
}
}
Task::~Task()
{
if (mCompletionSync) {
//printf(" -- Task %p dtor completion %p\n", this, mCompletionSync);
mCompletionSync->Signal();
mCompletionSync = nullptr;
}
}
TaskStatus
SetEventTask::Run()
{
mEvent->Set();
return TaskStatus::Complete;
}
SetEventTask::SetEventTask(MultiThreadedTaskQueue* aQueue,
SyncObject* aStart, SyncObject* aCompletion)
: Task(aQueue, aStart, aCompletion)
{
mEvent = new EventObject();
}
SetEventTask::~SetEventTask()
{}
SyncObject::SyncObject()
: mSignals(0)
, mHasSubmittedSubsequent(false)
{}
SyncObject::~SyncObject()
{
MOZ_ASSERT(mWaitingTasks.size() == 0);
}
bool
SyncObject::Register(Task* aTask)
{
MOZ_ASSERT(aTask);
mHasSubmittedSubsequent = true;
int32_t signals = mSignals;
if (signals > 0) {
AddWaitingTask(aTask);
// Since Register and Signal can be called concurrently, it can happen that
// reading mSignals in Register happens before decrementing mSignals in Signal,
// but SubmitWaitingTasks happens before AddWaitingTask. This ordering means
// the SyncObject ends up in the signaled state with a task sitting in the
// waiting list. To prevent that we check mSignals a second time and submit
// again if signals reached zero in the mean time.
// We do this instead of holding a mutex around mSignals+mTasks to reduce
// lock contention.
int32_t signals2 = mSignals;
if (signals2 == 0) {
SubmitWaitingTasks();
}
return true;
}
return false;
}
void
SyncObject::Signal()
{
// Fetch mHasSubmittedSubsequent *before* mSignals to avoid a race condition
// where signals reach zero before we have created all of the prerequisites
// which can lead to SubmitTasks being called with subsequents added in the
// mean time if the thread is interrupted between the read from mSignals and
// the read from mHasSubmittedSubsequents.
bool hasSubmittedSubsequent = mHasSubmittedSubsequent;
int32_t signals = --mSignals;
MOZ_ASSERT(signals >= 0);
if (hasSubmittedSubsequent && signals == 0) {
SubmitWaitingTasks();
}
}
void
SyncObject::AddWaitingTask(Task* aTask)
{
CriticalSectionAutoEnter lock(&mWaitingTasksSection);
mWaitingTasks.push_back(aTask);
}
void SyncObject::SubmitWaitingTasks()
{
std::vector<Task*> tasksToSubmit;
{
// Scheduling the tasks can cause code that modifies <this>'s reference
// count to run concurrently, and cause the caller of this function to
// be owned by another thread. We need to make sure the reference count
// does not reach 0 on another thread before mWaitingTasks.clear(), so
// hold a strong ref to prevent that!
RefPtr<SyncObject> kungFuDeathGrip(this);
CriticalSectionAutoEnter lock(&mWaitingTasksSection);
tasksToSubmit = Move(mWaitingTasks);
mWaitingTasks.clear();
}
for (Task* task : tasksToSubmit) {
task->GetTaskQueue()->SubmitTask(task);
}
}
bool
SyncObject::IsSignaled()
{
return mSignals == 0;
}
void
SyncObject::AddPrerequisite(Task* aTask)
{
#ifdef DEBUG
mPrerequisites.push_back(aTask);
#endif
// If this assertion blows up it means that a Task that depends on this sync
// object has been submitted before we declared all of the prerequisites.
// This is racy because if mSignals reaches zero before all prerequisites
// have been declared, a subsequent can be scheduled before the completion
// of the undeclared prerequisites.
MOZ_ASSERT(!mHasSubmittedSubsequent);
mSignals++;
}
void
SyncObject::AddSubsequent(Task* aTask)
{
#ifdef DEBUG
mSubsequents.push_back(aTask);
#endif
}
WorkerThread::WorkerThread(MultiThreadedTaskQueue* aTaskQueue)
: mQueue(aTaskQueue)
{
aTaskQueue->RegisterThread();
}
void
WorkerThread::Run()
{
for (;;) {
Task* commands = nullptr;
if (!mQueue->WaitForTask(commands)) {
mQueue->UnregisterThread();
return;
}
TaskStatus status = TaskScheduler::ProcessTask(commands);
if (status == TaskStatus::Error) {
// Don't try to handle errors for now, but that's open to discussions.
// I expect errors to be mostly OOM issues.
MOZ_CRASH();
}
}
}
} //namespace
} //namespace

View File

@ -1,225 +0,0 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZILLA_GFX_TASKSCHEDULER_H_
#define MOZILLA_GFX_TASKSCHEDULER_H_
#include "mozilla/RefPtr.h"
#include "mozilla/gfx/Types.h"
#ifdef WIN32
#include "mozilla/gfx/TaskScheduler_win32.h"
#else
#include "mozilla/gfx/TaskScheduler_posix.h"
#endif
#include <vector>
namespace mozilla {
namespace gfx {
class MultiThreadedTaskQueue;
class SyncObject;
class WorkerThread;
class TaskScheduler {
public:
/// Return one of the queues that the drawing worker threads pull from, chosen
/// pseudo-randomly.
static MultiThreadedTaskQueue* GetDrawingQueue()
{
return sSingleton->mDrawingQueues[
sSingleton->mNextQueue++ % sSingleton->mDrawingQueues.size()
];
}
/// Return one of the queues that the drawing worker threads pull from with a
/// hash to choose the queue.
///
/// Calling this function several times with the same hash will yield the same queue.
static MultiThreadedTaskQueue* GetDrawingQueue(uint32_t aHash)
{
return sSingleton->mDrawingQueues[
aHash % sSingleton->mDrawingQueues.size()
];
}
/// Initialize the task scheduler with aNumThreads worker threads for drawing
/// and aNumQueues task queues.
///
/// The number of threads must be superior or equal to the number of queues
/// (since for now a worker thread only pulls from one queue).
static bool Init(uint32_t aNumThreads, uint32_t aNumQueues);
/// Shut the scheduler down.
///
/// This will block until worker threads are joined and deleted.
static void ShutDown();
/// Returns true if there is a successfully initialized TaskScheduler singleton.
static bool IsEnabled() { return !!sSingleton; }
/// Submit a task buffer to its associated queue.
///
/// The caller looses ownership of the task buffer.
static void SubmitTask(Task* aTasks);
/// Process commands until the command buffer needs to block on a sync object,
/// completes, yields, or encounters an error.
///
/// Can be used on any thread. Worker threads basically loop over this, but the
/// main thread can also dequeue pending task buffers and process them alongside
/// the worker threads if it is about to block until completion anyway.
///
/// The caller looses ownership of the task buffer.
static TaskStatus ProcessTask(Task* aTasks);
protected:
static TaskScheduler* sSingleton;
// queues of Task that are ready to be processed
std::vector<MultiThreadedTaskQueue*> mDrawingQueues;
std::vector<WorkerThread*> mWorkerThreads;
Atomic<uint32_t> mNextQueue;
};
/// Tasks are not reference-counted because they don't have shared ownership.
/// The ownership of tasks can change when they are passed to certain methods
/// of TaskScheduler and SyncObject. See the docuumentaion of these classes.
class Task {
public:
Task(MultiThreadedTaskQueue* aQueue, SyncObject* aStart = nullptr, SyncObject* aCompletion = nullptr);
virtual ~Task();
virtual TaskStatus Run() = 0;
/// For use in TaskScheduler::SubmitTask. Don't use it anywhere else.
//already_AddRefed<SyncObject> GetAndResetStartSync();
SyncObject* GetStartSync() { return mStartSync; }
MultiThreadedTaskQueue* GetTaskQueue() { return mQueue; }
protected:
MultiThreadedTaskQueue* mQueue;
RefPtr<SyncObject> mStartSync;
RefPtr<SyncObject> mCompletionSync;
};
class EventObject;
/// This task will set an EventObject.
///
/// Typically used as the final task, so that the main thread can block on the
/// corresponfing EventObject until all of the tasks are processed.
class SetEventTask : public Task
{
public:
explicit SetEventTask(MultiThreadedTaskQueue* aQueue,
SyncObject* aStart = nullptr, SyncObject* aCompletion = nullptr);
~SetEventTask();
TaskStatus Run() override;
EventObject* GetEvent() { return mEvent; }
protected:
RefPtr<EventObject> mEvent;
};
/// A synchronization object that can be used to express dependencies and ordering between
/// tasks.
///
/// Tasks can register to SyncObjects in order to asynchronously wait for a signal.
/// In practice, Task objects usually start with a sync object (startSyc) and end
/// with another one (completionSync).
/// a Task never gets processed before its startSync is in the signaled state, and
/// signals its completionSync as soon as it finishes. This is how dependencies
/// between tasks is expressed.
class SyncObject final : public external::AtomicRefCounted<SyncObject> {
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(SyncObject)
/// Create a synchronization object.
SyncObject();
~SyncObject();
/// Attempt to register a task.
///
/// If the sync object is already in the signaled state, the buffer is *not*
/// registered and the sync object does not take ownership of the task.
/// If the object is not yet in the signaled state, it takes ownership of
/// the task and places it in a list of pending tasks.
/// Pending tasks will not be processed by the worker thread.
/// When the SyncObject reaches the signaled state, it places the pending
/// tasks back in the available buffer queue, so that they can be
/// scheduled again.
///
/// Returns true if the SyncOject is not already in the signaled state.
/// This means that if this method returns true, the SyncObject has taken
/// ownership of the Task.
bool Register(Task* aTask);
/// Signal the SyncObject.
///
/// This decrements an internal counter. The sync object reaches the signaled
/// state when the counter gets to zero.
/// calling Signal on a SyncObject that is already in the signaled state has
/// no effect.
void Signal();
/// Returns true if mSignals is equal to zero. In other words, returns true
/// if all subsequent tasks have already signaled the sync object.
///
/// Note that this means SyncObject are initially in the signaled state, until
/// a Task is created with and declares the sync objects as its "completion sync"
bool IsSignaled();
private:
// Called by Task's constructor
void AddSubsequent(Task* aTask);
void AddPrerequisite(Task* aTask);
void AddWaitingTask(Task* aTask);
void SubmitWaitingTasks();
#ifdef DEBUG
// For debugging purposes only.
std::vector<Task*> mPrerequisites;
std::vector<Task*> mSubsequents;
#endif
std::vector<Task*> mWaitingTasks;
CriticalSection mWaitingTasksSection; // for concurrent access to mWaintingTasks
Atomic<uint32_t> mSignals;
Atomic<bool> mHasSubmittedSubsequent;
friend class Task;
friend class TaskScheduler;
};
/// Base class for worker threads.
class WorkerThread
{
public:
static WorkerThread* Create(MultiThreadedTaskQueue* aTaskQueue);
virtual ~WorkerThread() {}
void Run();
protected:
explicit WorkerThread(MultiThreadedTaskQueue* aTaskQueue);
MultiThreadedTaskQueue* mQueue;
};
} // namespace
} // namespace
#endif

View File

@ -1,179 +0,0 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TaskScheduler.h"
#include "mozilla/gfx/Logging.h"
using namespace std;
namespace mozilla {
namespace gfx {
void* ThreadCallback(void* threadData);
class WorkerThreadPosix : public WorkerThread {
public:
explicit WorkerThreadPosix(MultiThreadedTaskQueue* aTaskQueue)
: WorkerThread(aTaskQueue)
{
pthread_create(&mThread, nullptr, ThreadCallback, static_cast<WorkerThread*>(this));
}
~WorkerThreadPosix()
{
pthread_join(mThread, nullptr);
}
protected:
pthread_t mThread;
};
void* ThreadCallback(void* threadData)
{
WorkerThread* thread = static_cast<WorkerThread*>(threadData);
thread->Run();
return nullptr;
}
WorkerThread*
WorkerThread::Create(MultiThreadedTaskQueue* aTaskQueue)
{
return new WorkerThreadPosix(aTaskQueue);
}
MultiThreadedTaskQueue::MultiThreadedTaskQueue()
: mThreadsCount(0)
, mShuttingDown(false)
{}
MultiThreadedTaskQueue::~MultiThreadedTaskQueue()
{
MOZ_ASSERT(mTasks.empty());
}
bool
MultiThreadedTaskQueue::WaitForTask(Task*& aOutTask)
{
return PopTask(aOutTask, BLOCKING);
}
bool
MultiThreadedTaskQueue::PopTask(Task*& aOutTasks, AccessType aAccess)
{
for (;;) {
MutexAutoLock lock(&mMutex);
while (aAccess == BLOCKING && !mShuttingDown && mTasks.empty()) {
mAvailableCondvar.Wait(&mMutex);
}
if (mShuttingDown) {
return false;
}
if (mTasks.empty()) {
if (aAccess == NON_BLOCKING) {
return false;
}
continue;
}
Task* task = mTasks.front();
MOZ_ASSERT(task);
mTasks.pop_front();
aOutTasks = task;
return true;
}
}
void
MultiThreadedTaskQueue::SubmitTask(Task* aTasks)
{
MOZ_ASSERT(aTasks);
MOZ_ASSERT(aTasks->GetTaskQueue() == this);
MutexAutoLock lock(&mMutex);
mTasks.push_back(aTasks);
mAvailableCondvar.Broadcast();
}
size_t
MultiThreadedTaskQueue::NumTasks()
{
MutexAutoLock lock(&mMutex);
return mTasks.size();
}
bool
MultiThreadedTaskQueue::IsEmpty()
{
MutexAutoLock lock(&mMutex);
return mTasks.empty();
}
void
MultiThreadedTaskQueue::ShutDown()
{
MutexAutoLock lock(&mMutex);
mShuttingDown = true;
while (mThreadsCount) {
mAvailableCondvar.Broadcast();
mShutdownCondvar.Wait(&mMutex);
}
}
void
MultiThreadedTaskQueue::RegisterThread()
{
mThreadsCount += 1;
}
void
MultiThreadedTaskQueue::UnregisterThread()
{
MutexAutoLock lock(&mMutex);
mThreadsCount -= 1;
if (mThreadsCount == 0) {
mShutdownCondvar.Broadcast();
}
}
EventObject::EventObject()
: mIsSet(false)
{}
EventObject::~EventObject()
{}
bool
EventObject::Peak()
{
MutexAutoLock lock(&mMutex);
return mIsSet;
}
void
EventObject::Set()
{
MutexAutoLock lock(&mMutex);
if (!mIsSet) {
mIsSet = true;
mCond.Broadcast();
}
}
void
EventObject::Wait()
{
MutexAutoLock lock(&mMutex);
if (mIsSet) {
return;
}
mCond.Wait(&mMutex);
}
} // namespce
} // namespce

View File

@ -1,144 +0,0 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef WIN32
#ifndef MOZILLA_GFX_TASKSCHEDULER_POSIX_H_
#define MOZILLA_GFX_TASKSCHEDULER_POSIX_H_
#include <string>
#include <vector>
#include <list>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include "mozilla/RefPtr.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/gfx/CriticalSection.h"
namespace mozilla {
namespace gfx {
class Task;
class PosixCondVar;
class WorkerThread;
typedef mozilla::gfx::CriticalSection Mutex;
typedef mozilla::gfx::CriticalSectionAutoEnter MutexAutoLock;
// posix platforms only!
class PosixCondVar {
public:
PosixCondVar() {
DebugOnly<int> err = pthread_cond_init(&mCond, nullptr);
MOZ_ASSERT(!err);
}
~PosixCondVar() {
DebugOnly<int> err = pthread_cond_destroy(&mCond);
MOZ_ASSERT(!err);
}
void Wait(Mutex* aMutex) {
DebugOnly<int> err = pthread_cond_wait(&mCond, &aMutex->mMutex);
MOZ_ASSERT(!err);
}
void Broadcast() {
DebugOnly<int> err = pthread_cond_broadcast(&mCond);
MOZ_ASSERT(!err);
}
protected:
pthread_cond_t mCond;
};
/// A simple and naive multithreaded task queue
///
/// The public interface of this class must remain identical to its equivalent
/// in TaskScheduler_win32.h
class MultiThreadedTaskQueue {
public:
enum AccessType {
BLOCKING,
NON_BLOCKING
};
// Producer thread
MultiThreadedTaskQueue();
// Producer thread
~MultiThreadedTaskQueue();
// Worker threads
bool WaitForTask(Task*& aOutTask);
// Any thread
bool PopTask(Task*& aOutTask, AccessType aAccess);
// Any threads
void SubmitTask(Task* aTask);
// Producer thread
void ShutDown();
// Any thread
size_t NumTasks();
// Any thread
bool IsEmpty();
// Producer thread
void RegisterThread();
// Worker threads
void UnregisterThread();
protected:
std::list<Task*> mTasks;
Mutex mMutex;
PosixCondVar mAvailableCondvar;
PosixCondVar mShutdownCondvar;
int32_t mThreadsCount;
bool mShuttingDown;
friend class WorkerThread;
};
/// An object that a thread can synchronously wait on.
/// Usually set by a SetEventTask.
class EventObject : public external::AtomicRefCounted<EventObject>
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
EventObject();
~EventObject();
/// Synchronously wait until the event is set.
void Wait();
/// Return true if the event is set, without blocking.
bool Peak();
/// Set the event.
void Set();
protected:
Mutex mMutex;
PosixCondVar mCond;
bool mIsSet;
};
} // namespace
} // namespace
#include "TaskScheduler.h"
#endif
#endif

View File

@ -1,144 +0,0 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TaskScheduler.h"
#include "mozilla/gfx/Logging.h"
using namespace std;
namespace mozilla {
namespace gfx {
DWORD __stdcall ThreadCallback(void* threadData);
class WorkerThreadWin32 : public WorkerThread {
public:
explicit WorkerThreadWin32(MultiThreadedTaskQueue* aTaskQueue)
: WorkerThread(aTaskQueue)
{
mThread = ::CreateThread(nullptr, 0, ThreadCallback, static_cast<WorkerThread*>(this), 0, nullptr);
}
~WorkerThreadWin32()
{
::WaitForSingleObject(mThread, INFINITE);
::CloseHandle(mThread);
}
protected:
HANDLE mThread;
};
DWORD __stdcall ThreadCallback(void* threadData)
{
WorkerThread* thread = static_cast<WorkerThread*>(threadData);
thread->Run();
return 0;
}
WorkerThread*
WorkerThread::Create(MultiThreadedTaskQueue* aTaskQueue)
{
return new WorkerThreadWin32(aTaskQueue);
}
bool
MultiThreadedTaskQueue::PopTask(Task*& aOutTask, AccessType aAccess)
{
for (;;) {
while (aAccess == BLOCKING && mTasks.empty()) {
{
CriticalSectionAutoEnter lock(&mSection);
if (mShuttingDown) {
return false;
}
}
HANDLE handles[] = { mAvailableEvent, mShutdownEvent };
::WaitForMultipleObjects(2, handles, FALSE, INFINITE);
}
CriticalSectionAutoEnter lock(&mSection);
if (mShuttingDown) {
return false;
}
if (mTasks.empty()) {
if (aAccess == NON_BLOCKING) {
return false;
}
continue;
}
Task* task = mTasks.front();
MOZ_ASSERT(task);
mTasks.pop_front();
if (mTasks.empty()) {
::ResetEvent(mAvailableEvent);
}
aOutTask = task;
return true;
}
}
void
MultiThreadedTaskQueue::SubmitTask(Task* aTask)
{
MOZ_ASSERT(aTask);
MOZ_ASSERT(aTask->GetTaskQueue() == this);
CriticalSectionAutoEnter lock(&mSection);
mTasks.push_back(aTask);
::SetEvent(mAvailableEvent);
}
void
MultiThreadedTaskQueue::ShutDown()
{
{
CriticalSectionAutoEnter lock(&mSection);
mShuttingDown = true;
}
while (mThreadsCount) {
::SetEvent(mAvailableEvent);
::WaitForSingleObject(mShutdownEvent, INFINITE);
}
}
size_t
MultiThreadedTaskQueue::NumTasks()
{
CriticalSectionAutoEnter lock(&mSection);
return mTasks.size();
}
bool
MultiThreadedTaskQueue::IsEmpty()
{
CriticalSectionAutoEnter lock(&mSection);
return mTasks.empty();
}
void
MultiThreadedTaskQueue::RegisterThread()
{
mThreadsCount += 1;
}
void
MultiThreadedTaskQueue::UnregisterThread()
{
CriticalSectionAutoEnter lock(&mSection);
mThreadsCount -= 1;
if (mThreadsCount == 0) {
::SetEvent(mShutdownEvent);
}
}
} // namespace
} // namespace

View File

@ -1,99 +0,0 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef WIN32
#ifndef MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
#define MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
#define NOT_IMPLEMENTED MOZ_CRASH("Not implemented")
#include "mozilla/RefPtr.h"
#include "mozilla/gfx/CriticalSection.h"
#include <windows.h>
#include <list>
namespace mozilla {
namespace gfx {
class WorkerThread;
class Task;
// The public interface of this class must remain identical to its equivalent
// in TaskScheduler_posix.h
class MultiThreadedTaskQueue {
public:
enum AccessType {
BLOCKING,
NON_BLOCKING
};
MultiThreadedTaskQueue()
: mThreadsCount(0)
, mShuttingDown(false)
{
mAvailableEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
mShutdownEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
}
~MultiThreadedTaskQueue()
{
::CloseHandle(mAvailableEvent);
::CloseHandle(mShutdownEvent);
}
bool WaitForTask(Task*& aOutTask) { return PopTask(aOutTask, BLOCKING); }
bool PopTask(Task*& aOutTask, AccessType aAccess);
void SubmitTask(Task* aTask);
void ShutDown();
size_t NumTasks();
bool IsEmpty();
void RegisterThread();
void UnregisterThread();
protected:
std::list<Task*> mTasks;
CriticalSection mSection;
HANDLE mAvailableEvent;
HANDLE mShutdownEvent;
int32_t mThreadsCount;
bool mShuttingDown;
friend class WorkerThread;
};
// The public interface of this class must remain identical to its equivalent
// in TaskScheduler_posix.h
class EventObject : public external::AtomicRefCounted<EventObject>
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
EventObject() { mEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); }
~EventObject() { ::CloseHandle(mEvent); }
void Wait() { ::WaitForSingleObject(mEvent, INFINITE); }
bool Peak() { return ::WaitForSingleObject(mEvent, 0) == WAIT_OBJECT_0; }
void Set() { ::SetEvent(mEvent); }
protected:
// TODO: it's expensive to create events so we should try to reuse them
HANDLE mEvent;
};
} // namespace
} // namespace
#endif
#endif

View File

@ -289,13 +289,6 @@ struct GradientStop
Color color;
};
enum class TaskStatus {
Complete,
Wait,
Yield,
Error
};
} // namespace gfx
} // namespace mozilla

View File

@ -20,13 +20,11 @@ EXPORTS.mozilla.gfx += [
'Blur.h',
'BorrowedContext.h',
'Coord.h',
'CriticalSection.h',
'DataSurfaceHelpers.h',
'DrawTargetTiled.h',
'Filters.h',
'Helpers.h',
'HelpersCairo.h',
'IterableArena.h',
'Logging.h',
'Matrix.h',
'NumericTools.h',
@ -40,9 +38,6 @@ EXPORTS.mozilla.gfx += [
'ScaleFactors2D.h',
'SourceSurfaceCairo.h',
'StackArray.h',
'TaskScheduler.h',
'TaskScheduler_posix.h',
'TaskScheduler_win32.h',
'Tools.h',
'Types.h',
'UserData.h',
@ -72,15 +67,9 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'SourceSurfaceD2D.cpp',
'SourceSurfaceD2D1.cpp',
'SourceSurfaceD2DTarget.cpp',
'TaskScheduler_win32.cpp',
]
DEFINES['WIN32'] = True
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'windows':
SOURCES += [
'TaskScheduler_posix.cpp',
]
if CONFIG['MOZ_ENABLE_SKIA']:
UNIFIED_SOURCES += [
'convolver.cpp',
@ -129,7 +118,6 @@ UNIFIED_SOURCES += [
'DataSourceSurface.cpp',
'DataSurfaceHelpers.cpp',
'DrawEventRecorder.cpp',
'DrawingTask.cpp',
'DrawTarget.cpp',
'DrawTargetCairo.cpp',
'DrawTargetCapture.cpp',
@ -152,7 +140,6 @@ UNIFIED_SOURCES += [
'ScaledFontCairo.cpp',
'SourceSurfaceCairo.cpp',
'SourceSurfaceRawData.cpp',
'TaskScheduler.cpp',
]
SOURCES += [

View File

@ -1,188 +0,0 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "mozilla/gfx/IterableArena.h"
#include <string>
using namespace mozilla;
using namespace mozilla::gfx;
#ifdef A
#undef A
#endif
#ifdef B
#undef B
#endif
// to avoid having symbols that collide easily like A and B in the global namespace
namespace test_arena {
class A;
class B;
class Base {
public:
virtual ~Base() {}
virtual A* AsA() { return nullptr; }
virtual B* AsB() { return nullptr; }
};
static int sDtorItemA = 0;
static int sDtorItemB = 0;
class A : public Base {
public:
virtual A* AsA() override { return this; }
explicit A(uint64_t val) : mVal(val) {}
~A() { ++sDtorItemA; }
uint64_t mVal;
};
class B : public Base {
public:
virtual B* AsB() override { return this; }
explicit B(const string& str) : mVal(str) {}
~B() { ++sDtorItemB; }
std::string mVal;
};
struct BigStruct {
uint64_t mVal;
uint8_t data[120];
explicit BigStruct(uint64_t val) : mVal(val) {}
};
void TestArenaAlloc(IterableArena::ArenaType aType)
{
sDtorItemA = 0;
sDtorItemB = 0;
IterableArena arena(aType, 256);
// An empty arena has no items to iterate over.
{
int iterations = 0;
arena.ForEach([&](void* item){
iterations++;
});
ASSERT_EQ(iterations, 0);
}
auto a1 = arena.Alloc<A>(42);
auto b1 = arena.Alloc<B>("Obladi oblada");
auto a2 = arena.Alloc<A>(1337);
auto b2 = arena.Alloc<B>("Yellow submarine");
auto b3 = arena.Alloc<B>("She's got a ticket to ride");
// Alloc returns a non-negative offset if the allocation succeeded.
ASSERT_TRUE(a1 >= 0);
ASSERT_TRUE(a2 >= 0);
ASSERT_TRUE(b1 >= 0);
ASSERT_TRUE(b2 >= 0);
ASSERT_TRUE(b3 >= 0);
ASSERT_TRUE(arena.GetStorage(a1) != nullptr);
ASSERT_TRUE(arena.GetStorage(a2) != nullptr);
ASSERT_TRUE(arena.GetStorage(b1) != nullptr);
ASSERT_TRUE(arena.GetStorage(b2) != nullptr);
ASSERT_TRUE(arena.GetStorage(b3) != nullptr);
ASSERT_TRUE(((Base*)arena.GetStorage(a1))->AsA() != nullptr);
ASSERT_TRUE(((Base*)arena.GetStorage(a2))->AsA() != nullptr);
ASSERT_TRUE(((Base*)arena.GetStorage(b1))->AsB() != nullptr);
ASSERT_TRUE(((Base*)arena.GetStorage(b2))->AsB() != nullptr);
ASSERT_TRUE(((Base*)arena.GetStorage(b3))->AsB() != nullptr);
ASSERT_EQ(((Base*)arena.GetStorage(a1))->AsA()->mVal, (uint64_t)42);
ASSERT_EQ(((Base*)arena.GetStorage(a2))->AsA()->mVal, (uint64_t)1337);
ASSERT_EQ(((Base*)arena.GetStorage(b1))->AsB()->mVal, std::string("Obladi oblada"));
ASSERT_EQ(((Base*)arena.GetStorage(b2))->AsB()->mVal, std::string("Yellow submarine"));
ASSERT_EQ(((Base*)arena.GetStorage(b3))->AsB()->mVal, std::string("She's got a ticket to ride"));
{
int iterations = 0;
arena.ForEach([&](void* item){
iterations++;
});
ASSERT_EQ(iterations, 5);
}
// Typically, running the destructors of the elements in the arena will is done
// manually like this:
arena.ForEach([](void* item){
((Base*)item)->~Base();
});
arena.Clear();
ASSERT_EQ(sDtorItemA, 2);
ASSERT_EQ(sDtorItemB, 3);
// An empty arena has no items to iterate over (we just cleared it).
{
int iterations = 0;
arena.ForEach([&](void* item){
iterations++;
});
ASSERT_EQ(iterations, 0);
}
}
void TestArenaLimit(IterableArena::ArenaType aType, bool aShouldReachLimit)
{
IterableArena arena(aType, 128);
// A non-growable arena should return a negative offset when running out
// of space, without crashing.
// We should not run out of space with a growable arena (unless the os is
// running out of memory but this isn't expected for this test).
bool reachedLimit = false;
for (int i = 0; i < 100; ++i) {
auto offset = arena.Alloc<A>(42);
if (offset < 0) {
reachedLimit = true;
break;
}
}
ASSERT_EQ(reachedLimit, aShouldReachLimit);
}
} // namespace test_arena
using namespace test_arena;
TEST(Moz2D, FixedArena) {
TestArenaAlloc(IterableArena::FIXED_SIZE);
TestArenaLimit(IterableArena::FIXED_SIZE, true);
}
TEST(Moz2D, GrowableArena) {
TestArenaAlloc(IterableArena::GROWABLE);
TestArenaLimit(IterableArena::GROWABLE, false);
IterableArena arena(IterableArena::GROWABLE, 16);
// sizeof(BigStruct) is more than twice the initial capacity, make sure that
// this doesn't blow everything up, since the arena doubles its storage size each
// time it grows (until it finds a size that fits).
auto a = arena.Alloc<BigStruct>(1);
auto b = arena.Alloc<BigStruct>(2);
auto c = arena.Alloc<BigStruct>(3);
// Offsets should also still point to the appropriate values after reallocation.
ASSERT_EQ(((BigStruct*)arena.GetStorage(a))->mVal, (uint64_t)1);
ASSERT_EQ(((BigStruct*)arena.GetStorage(b))->mVal, (uint64_t)2);
ASSERT_EQ(((BigStruct*)arena.GetStorage(c))->mVal, (uint64_t)3);
arena.Clear();
}

View File

@ -1,277 +0,0 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "mozilla/gfx/TaskScheduler.h"
#ifndef WIN32
#include <pthread.h>
#include <sched.h>
#endif
#include <stdlib.h>
#include <time.h>
namespace test_scheduler {
using namespace mozilla::gfx;
using namespace mozilla;
// Artificially cause threads to yield randomly in an attempt to make racy
// things more apparent (if any).
void MaybeYieldThread()
{
#ifndef WIN32
if (rand() % 5 == 0) {
sched_yield();
}
#endif
}
/// Used by the TestCommand to check that tasks are processed in the right order.
struct SanityChecker {
std::vector<uint64_t> mAdvancements;
mozilla::gfx::CriticalSection mSection;
explicit SanityChecker(uint64_t aNumCmdBuffers)
{
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
mAdvancements.push_back(0);
}
}
virtual void Check(uint64_t aTaskId, uint64_t aCmdId)
{
MaybeYieldThread();
CriticalSectionAutoEnter lock(&mSection);
ASSERT_EQ(mAdvancements[aTaskId], aCmdId-1);
mAdvancements[aTaskId] = aCmdId;
}
};
/// Run checks that are specific to TestSchulerJoin.
struct JoinTestSanityCheck : public SanityChecker {
bool mSpecialTaskHasRun;
explicit JoinTestSanityCheck(uint64_t aNumCmdBuffers)
: SanityChecker(aNumCmdBuffers)
, mSpecialTaskHasRun(false)
{}
virtual void Check(uint64_t aTaskId, uint64_t aCmdId) override
{
// Task 0 is the special task executed when everything is joined after task 1
if (aCmdId == 0) {
ASSERT_FALSE(mSpecialTaskHasRun);
mSpecialTaskHasRun = true;
for (auto advancement : mAdvancements) {
// Because of the synchronization point (beforeFilter), all
// task buffers should have run task 1 when task 0 is run.
ASSERT_EQ(advancement, (uint32_t)1);
}
} else {
// This check does not apply to task 0.
SanityChecker::Check(aTaskId, aCmdId);
}
if (aCmdId == 2) {
ASSERT_TRUE(mSpecialTaskHasRun);
}
}
};
// A poor man's leak check.
Atomic<int32_t> sTaskCount(0);
class TestTask : public Task
{
public:
TestTask(uint64_t aCmdId, uint64_t aTaskId, SanityChecker* aChecker,
MultiThreadedTaskQueue* aQueue,
SyncObject* aStart, SyncObject* aCompletion)
: Task(aQueue, aStart, aCompletion)
, mCmdId(aCmdId)
, mCmdBufferId(aTaskId)
, mSanityChecker(aChecker)
{
++sTaskCount;
}
~TestTask()
{
--sTaskCount;
}
TaskStatus Run()
{
MaybeYieldThread();
mSanityChecker->Check(mCmdBufferId, mCmdId);
MaybeYieldThread();
return TaskStatus::Complete;
}
uint64_t mCmdId;
uint64_t mCmdBufferId;
SanityChecker* mSanityChecker;
};
void Init()
{
ASSERT_EQ(sTaskCount, 0);
sTaskCount = 0;
}
void ShutDown()
{
// If this fails we probably leaked Task objects, or we shut the test down
// before completing all tasks.
ASSERT_EQ(sTaskCount, 0);
sTaskCount = 0;
}
/// This test creates aNumCmdBuffers task buffers with sync objects set up
/// so that all tasks will join after command 5 before a task buffer runs
/// a special task (task 0) after which all task buffers fork again.
/// This simulates the kind of scenario where all tiles must join at
/// a certain point to execute, say, a filter, and fork again after the filter
/// has been processed.
/// The main thread is only blocked when waiting for the completion of the entire
/// task stream (it doesn't have to wait at the filter's sync points to orchestrate it).
void TestSchedulerJoin(uint32_t aNumThreads, uint32_t aNumCmdBuffers)
{
JoinTestSanityCheck check(aNumCmdBuffers);
RefPtr<SyncObject> beforeFilter = new SyncObject();
RefPtr<SyncObject> afterFilter = new SyncObject();
RefPtr<SyncObject> completion = new SyncObject();
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
Task* t1 = new TestTask(1, i, &check, TaskScheduler::GetDrawingQueue(),
nullptr, beforeFilter);
TaskScheduler::SubmitTask(t1);
MaybeYieldThread();
}
// This task buffer is executed when all other tasks have joined after task 1
TaskScheduler::SubmitTask(
new TestTask(0, 0, &check, TaskScheduler::GetDrawingQueue(), beforeFilter, afterFilter)
);
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
Task* t2 = new TestTask(2, i, &check, TaskScheduler::GetDrawingQueue(),
afterFilter, completion);
TaskScheduler::SubmitTask(t2);
MaybeYieldThread();
}
auto evtTask = new SetEventTask(TaskScheduler::GetDrawingQueue(), completion);
RefPtr<EventObject> waitForCompletion = evtTask->GetEvent();
TaskScheduler::SubmitTask(evtTask);
MaybeYieldThread();
waitForCompletion->Wait();
MaybeYieldThread();
for (auto advancement : check.mAdvancements) {
ASSERT_TRUE(advancement == 2);
}
}
/// This test creates several chains of 10 task, tasks of a given chain are executed
/// sequentially, and chains are exectuted in parallel.
/// This simulates the typical scenario where we want to process sequences of drawing
/// commands for several tiles in parallel.
void TestSchedulerChain(uint32_t aNumThreads, uint32_t aNumCmdBuffers)
{
SanityChecker check(aNumCmdBuffers);
RefPtr<SyncObject> completion = new SyncObject();
uint32_t numTasks = 10;
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
std::vector<RefPtr<SyncObject>> syncs;
std::vector<Task*> tasks;
syncs.reserve(numTasks);
tasks.reserve(numTasks);
for (uint32_t t = 0; t < numTasks-1; ++t) {
syncs.push_back(new SyncObject());
tasks.push_back(new TestTask(t+1, i, &check, TaskScheduler::GetDrawingQueue(),
t == 0 ? nullptr : syncs[t-1].get(),
syncs[t]));
}
tasks.push_back(new TestTask(numTasks, i, &check,
TaskScheduler::GetDrawingQueue(),
syncs.back(), completion));
if (i % 2 == 0) {
// submit half of the tasks in order
for (Task* task : tasks) {
TaskScheduler::SubmitTask(task);
MaybeYieldThread();
}
} else {
// ... and submit the other half in reverse order
for (int32_t reverse = numTasks-1; reverse >= 0; --reverse) {
TaskScheduler::SubmitTask(tasks[reverse]);
MaybeYieldThread();
}
}
}
auto evtTask = new SetEventTask(TaskScheduler::GetDrawingQueue(), completion);
RefPtr<EventObject> waitForCompletion = evtTask->GetEvent();
TaskScheduler::SubmitTask(evtTask);
MaybeYieldThread();
waitForCompletion->Wait();
for (auto advancement : check.mAdvancements) {
ASSERT_TRUE(advancement == numTasks);
}
}
} // namespace test_scheduler
TEST(Moz2D, TaskScheduler_Join) {
srand(time(nullptr));
for (uint32_t threads = 1; threads < 16; ++threads) {
for (uint32_t queues = 1; queues < threads; ++queues) {
for (uint32_t buffers = 1; buffers < 100; buffers += 3) {
test_scheduler::Init();
mozilla::gfx::TaskScheduler::Init(threads, queues);
test_scheduler::TestSchedulerJoin(threads, buffers);
mozilla::gfx::TaskScheduler::ShutDown();
test_scheduler::ShutDown();
}
}
}
}
TEST(Moz2D, TaskScheduler_Chain) {
srand(time(nullptr));
for (uint32_t threads = 1; threads < 16; ++threads) {
for (uint32_t queues = 1; queues < threads; ++queues) {
for (uint32_t buffers = 1; buffers < 50; buffers += 3) {
test_scheduler::Init();
mozilla::gfx::TaskScheduler::Init(threads, queues);
test_scheduler::TestSchedulerChain(threads, buffers);
mozilla::gfx::TaskScheduler::ShutDown();
test_scheduler::ShutDown();
}
}
}
}

View File

@ -8,7 +8,6 @@ UNIFIED_SOURCES += [
'gfxSurfaceRefCountTest.cpp',
# Disabled on suspicion of causing bug 904227
#'gfxWordCacheTest.cpp',
'TestArena.cpp',
'TestAsyncPanZoomController.cpp',
'TestBufferRotation.cpp',
'TestColorNames.cpp',
@ -21,7 +20,6 @@ UNIFIED_SOURCES += [
'TestRect.cpp',
'TestRegion.cpp',
'TestSkipChars.cpp',
'TestTaskScheduler.cpp',
# Hangs on linux in ApplyGdkScreenFontOptions
#'gfxFontSelectionTest.cpp',
'TestTextures.cpp',