mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
5acea77a37
commit
1eefe94873
@ -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
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -289,13 +289,6 @@ struct GradientStop
|
||||
Color color;
|
||||
};
|
||||
|
||||
enum class TaskStatus {
|
||||
Complete,
|
||||
Wait,
|
||||
Yield,
|
||||
Error
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -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 += [
|
||||
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user