Restore "Add single-threaded config to Filament. (#130)"
This reverts commit 24022010f9.
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -343,6 +343,8 @@ public:
|
||||
return mDebugRegistry;
|
||||
}
|
||||
|
||||
bool execute();
|
||||
|
||||
private:
|
||||
FEngine(Backend backend, ExternalContext* externalContext, void* sharedGLContext);
|
||||
void init();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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__)
|
||||
|
||||
Reference in New Issue
Block a user