Restore "Add single-threaded config to Filament. (#130)"

This reverts commit 24022010f9.
This commit is contained in:
prideout
2018-08-27 08:13:43 -07:00
committed by Romain Guy
parent 33c44ee9ce
commit 3a7d80f29b
10 changed files with 123 additions and 50 deletions

View File

@@ -335,6 +335,13 @@ public:
destroy(camera->getEntity());
}
/**
* Invokes one iteration of the render loop, used only on single-threaded platforms.
*
* This should be called every time the windowing system needs to paint (e.g. at 60 Hz).
*/
void execute();
DebugRegistry& getDebugRegistry() noexcept;
protected:

View File

@@ -74,11 +74,22 @@ static std::mutex sEnginesLock;
FEngine* FEngine::create(Backend backend, ExternalContext* externalContext, void* sharedGLContext) {
FEngine* instance = new FEngine(backend, externalContext, sharedGLContext);
slog.i << "FEngine (" << sizeof(void*) * 8 << " bits) created at " << instance << io::endl;
slog.i << "FEngine (" << sizeof(void*) * 8 << " bits) created at " << instance << " "
<< "(threading is " << (UTILS_HAS_THREADING ? "enabled)" : "disabled)") << io::endl;
// initialize all fields that need an instance of FEngine
// (this cannot be done safely in the ctor)
// Normally we launch a thread and create the context and Driver from there (see FEngine::loop).
// In the single-threaded case, we do so in the here and now.
if (!UTILS_HAS_THREADING) {
instance->mExternalContext = ExternalContext::create(&instance->mBackend);
instance->mDriver = instance->mExternalContext->createDriver(sharedGLContext);
instance->init();
instance->execute();
return instance;
}
// start the driver thread
instance->mDriverThread = std::thread(&FEngine::loop, instance);
@@ -292,13 +303,18 @@ void FEngine::shutdown() {
// There might be commands added by the terminate() calls
flushCommandBuffer(mCommandBufferQueue);
if (!UTILS_HAS_THREADING) {
execute();
}
/*
* terminate the rendering engine
*/
mCommandBufferQueue.requestExit();
mDriverThread.join();
if (UTILS_HAS_THREADING) {
mDriverThread.join();
}
mTerminated = true;
// detach this thread from the jobsystem
@@ -362,28 +378,18 @@ int FEngine::loop() {
JobSystem::setThreadName("FEngine::loop");
JobSystem::setThreadPriority(JobSystem::Priority::DISPLAY);
// FIXME: we should do this based on the CPUs we actually have
uint32_t affinityMask = (std::thread::hardware_concurrency() >= 6) ? 0xF0 : 0;
auto& commandBufferQueue = mCommandBufferQueue;
while (true) {
// wait until we get command buffers to be executed (or thread exit requested)
auto buffers = commandBufferQueue.waitForCommands();
if (UTILS_UNLIKELY(!buffers.size())) {
break;
}
// FIXME: we should do this based on the CPUs we actually have
uint32_t affinityMask = (std::thread::hardware_concurrency() >= 6) ? 0xF0 : 0;
if (affinityMask) {
// looks like thread affinity needs to be reset regularly (on Android)
JobSystem::setThreadAffinity(affinityMask);
}
// execute all command buffers
for (auto& item : buffers) {
if (UTILS_LIKELY(item.begin)) {
mCommandStream.execute(item.begin);
mCommandBufferQueue.releaseBuffer(item);
}
if (!execute()) {
break;
}
}
@@ -699,6 +705,25 @@ void* FEngine::streamAlloc(size_t size, size_t alignment) noexcept {
return getDriverApi().allocate(size, alignment);
}
bool FEngine::execute() {
// wait until we get command buffers to be executed (or thread exit requested)
auto buffers = mCommandBufferQueue.waitForCommands();
if (UTILS_UNLIKELY(!buffers.size())) {
return false;
}
// execute all command buffers
for (auto& item : buffers) {
if (UTILS_LIKELY(item.begin)) {
mCommandStream.execute(item.begin);
mCommandBufferQueue.releaseBuffer(item);
}
}
return true;
}
// ---------------------------------------------------------------------------------------------
EnginePerformanceTest::~EnginePerformanceTest() noexcept = default;
@@ -871,6 +896,14 @@ void* Engine::streamAlloc(size_t size, size_t alignment) noexcept {
return upcast(this)->streamAlloc(size, alignment);
}
// The external-facing execute does a flush, and is meant only for single-threaded environments.
// It also discards the boolean return value, which would otherwise indicate a thread exit.
void Engine::execute() {
ASSERT_PRECONDITION(!UTILS_HAS_THREADING, "Execute is meant for single-threaded platforms.");
upcast(this)->flush();
upcast(this)->execute();
}
DebugRegistry& Engine::getDebugRegistry() noexcept {
return upcast(this)->getDebugRegistry();
}

View File

@@ -20,6 +20,8 @@
#include <filament/Fence.h>
#include <utils/Panic.h>
namespace filament {
using namespace driver;
@@ -66,6 +68,8 @@ FenceStatus FFence::waitAndDestroy(FFence* fence, Mode mode) noexcept {
UTILS_NOINLINE
FenceStatus FFence::wait(Mode mode, uint64_t timeout) noexcept {
ASSERT_PRECONDITION(UTILS_HAS_THREADING || timeout == 0, "Non-zero timeout requires threads.");
FEngine& engine = mEngine;
if (mode == Mode::FLUSH) {

View File

@@ -58,7 +58,9 @@ void FRenderer::init() noexcept {
mRenderTarget = driver.createDefaultRenderTarget();
mIsRGB16FSupported = driver.isRenderTargetFormatSupported(driver::TextureFormat::RGB16F);
mIsRGB8Supported = driver.isRenderTargetFormatSupported(driver::TextureFormat::RGB8);
mFrameInfoManager.run();
if (UTILS_HAS_THREADING) {
mFrameInfoManager.run();
}
}
FRenderer::~FRenderer() noexcept {
@@ -83,8 +85,14 @@ void FRenderer::terminate(FEngine& engine) {
// before we can destroy this Renderer's resources, we must make sure
// that all pending commands have been executed (as they could reference data in this
// instance, e.g. Fences, Callbacks, etc...)
Fence::waitAndDestroy(engine.createFence());
mFrameInfoManager.terminate();
if (UTILS_HAS_THREADING) {
Fence::waitAndDestroy(engine.createFence());
mFrameInfoManager.terminate();
} else {
// In single threaded mode, allow recently-created objects (e.g. no-op fences in Skipper)
// to initialize themselves, otherwise the engine tries to destroy invalid handles.
engine.execute();
}
}
void FRenderer::render(FView const* view) {
@@ -233,7 +241,9 @@ bool FRenderer::beginFrame(FSwapChain* swapChain) {
assert(swapChain);
mFrameId++;
mFrameInfoManager.beginFrame(mFrameId);
if (UTILS_HAS_THREADING) {
mFrameInfoManager.beginFrame(mFrameId);
}
{ // scope for frame id trace
char buf[64];
@@ -273,12 +283,16 @@ void FRenderer::endFrame() {
FEngine::DriverApi& driver = engine.getDriverApi();
RenderTargetPool& rtp = engine.getRenderTargetPool();
// on debug builds this helps catching cases where we're writing to
// the buffer form another thread, which is currently not allowed.
driver.debugThreading();
FrameInfoManager& frameInfoManager = mFrameInfoManager;
frameInfoManager.endFrame();
if (UTILS_HAS_THREADING) {
// on debug builds this helps catching cases where we're writing to
// the buffer form another thread, which is currently not allowed.
driver.debugThreading();
frameInfoManager.endFrame();
}
mFrameSkipper.endFrame();
driver.endFrame(mFrameId);

View File

@@ -343,6 +343,8 @@ public:
return mDebugRegistry;
}
bool execute();
private:
FEngine(Backend backend, ExternalContext* externalContext, void* sharedGLContext);
void init();

View File

@@ -102,9 +102,11 @@ void CommandBufferQueue::flush() noexcept {
}
std::vector<CommandBufferQueue::Slice> CommandBufferQueue::waitForCommands() const {
std::unique_lock<utils::Mutex> lock(mLock);
while (mCommandBuffersToExecute.empty() && !mExitRequested) {
mCondition.wait(lock);
if (UTILS_HAS_THREADING) {
std::unique_lock<utils::Mutex> lock(mLock);
while (mCommandBuffersToExecute.empty() && !mExitRequested) {
mCondition.wait(lock);
}
}
return std::move(mCommandBuffersToExecute);
}

View File

@@ -28,7 +28,7 @@
namespace filament {
/*
* A produdcer-consumer command queue that uses a CircularBuffer as main storage
* A producer-consumer command queue that uses a CircularBuffer as main storage
*/
class CommandBufferQueue {
struct Slice {

View File

@@ -72,6 +72,11 @@
# define UTILS_HAS_HYPER_THREADING 0
#endif
#if defined(__EMSCRIPTEN__)
# define UTILS_HAS_THREADING 0
#else
# define UTILS_HAS_THREADING 1
#endif
#if __has_attribute(noinline)
#define UTILS_NOINLINE __attribute__((noinline))

View File

@@ -120,7 +120,7 @@ JobSystem::JobSystem(size_t threadCount, size_t adoptableThreadsCount) noexcept
threadCount = hwThreads - 1;
}
}
threadCount = std::min(size_t(32), threadCount);
threadCount = std::min(size_t(UTILS_HAS_THREADING ? 32 : 0), threadCount);
mThreadStates = aligned_vector<ThreadState>(threadCount + adoptableThreadsCount);
mThreadCount = uint16_t(threadCount);

View File

@@ -74,29 +74,27 @@ FilamentApp::~FilamentApp() {
SDL_Quit();
}
void FilamentApp::run(const Config& config,SetupCallback setupCallback,
void FilamentApp::run(const Config& config, SetupCallback setupCallback,
CleanupCallback cleanupCallback, ImGuiCallback imguiCallback,
PreRenderCallback preRender, PostRenderCallback postRender,
size_t width, size_t height) {
mEngine = Engine::create(config.backend);
mDepthMaterial = Material::Builder()
.package((void*) DEPTH_VISUALIZER_PACKAGE, sizeof(DEPTH_VISUALIZER_PACKAGE))
.build(*mEngine);
mDepthMI = mDepthMaterial->createInstance();
mTransparentMaterial = Material::Builder()
.package((void*) TRANSPARENT_COLOR_PACKAGE, sizeof(TRANSPARENT_COLOR_PACKAGE))
.build(*mEngine);
mDefaultMaterial = Material::Builder()
.package((void*) AI_DEFAULT_MAT_PACKAGE, sizeof(AI_DEFAULT_MAT_PACKAGE))
.build(*mEngine);
std::unique_ptr<FilamentApp::Window> window(
new FilamentApp::Window(this, config, config.title, width, height));
mDepthMaterial = Material::Builder()
.package((void*) DEPTH_VISUALIZER_PACKAGE, sizeof(DEPTH_VISUALIZER_PACKAGE))
.build(*mEngine);
mDepthMI = mDepthMaterial->createInstance();
mDefaultMaterial = Material::Builder()
.package((void*) AI_DEFAULT_MAT_PACKAGE, sizeof(AI_DEFAULT_MAT_PACKAGE))
.build(*mEngine);
mTransparentMaterial = Material::Builder()
.package((void*) TRANSPARENT_COLOR_PACKAGE, sizeof(TRANSPARENT_COLOR_PACKAGE))
.build(*mEngine);
std::unique_ptr<Cube> cameraCube(new Cube(*mEngine, mTransparentMaterial, {1,0,0}));
// we can't cull the light-frustum because it's not applied a rigid transform
// and currently, filament assumes that for culling
@@ -206,6 +204,10 @@ void FilamentApp::run(const Config& config,SetupCallback setupCallback,
while (!mClosed) {
if (!UTILS_HAS_THREADING) {
mEngine->execute();
}
// Allow the app to animate the scene if desired.
if (mAnimation) {
double now = (double) SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency();
@@ -423,10 +425,14 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
: mFilamentApp(filamentApp) {
const int x = SDL_WINDOWPOS_CENTERED;
const int y = SDL_WINDOWPOS_CENTERED;
const uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL;
const uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
mWindow = SDL_CreateWindow(title.c_str(), x, y, (int) w, (int) h, windowFlags);
// Create the Engine after the window in case this happens to be a single-threaded platform.
// For single-threaded platforms, we need to ensure that Filament's OpenGL context is current,
// rather than the one created by SDL.
mFilamentApp->mEngine = Engine::create(config.backend);
// HACK: We don't use SDL's 2D rendering functionality, but by invoking it we cause
// SDL to create a Metal backing layer, which allows us to run Vulkan apps via MoltenVK.
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) && defined(__APPLE__)