From 0d64d6cf6fdf2f51071debca9fbd6a41606fafdb Mon Sep 17 00:00:00 2001 From: alasram Date: Tue, 6 Aug 2024 16:45:46 -0700 Subject: [PATCH] Add Free-Threaded resource upload to Android OpenGL Backend --- CMakeLists.txt | 4 + bazel/core.bzl | 4 + include/mbgl/gl/drawable_gl.hpp | 2 +- include/mbgl/gl/layer_group_gl.hpp | 4 +- include/mbgl/gl/renderer_backend.hpp | 21 ++ include/mbgl/mtl/layer_group.hpp | 2 +- include/mbgl/mtl/tile_layer_group.hpp | 2 +- include/mbgl/renderer/layer_group.hpp | 2 +- include/mbgl/renderer/render_target.hpp | 2 +- include/mbgl/util/string.hpp | 5 + include/mbgl/vulkan/layer_group.hpp | 2 +- include/mbgl/vulkan/tile_layer_group.hpp | 2 +- .../src/cpp/android_gl_renderer_backend.cpp | 238 +++++++++++++++++- .../src/cpp/android_gl_renderer_backend.hpp | 38 +++ .../include/mbgl/gl/headless_backend.hpp | 2 +- src/mbgl/gfx/attribute.hpp | 2 +- src/mbgl/gl/buffer_resource.cpp | 156 ++++++++++++ src/mbgl/gl/buffer_resource.hpp | 81 ++++++ src/mbgl/gl/context.cpp | 7 + src/mbgl/gl/context.hpp | 6 + src/mbgl/gl/drawable_gl.cpp | 50 +--- src/mbgl/gl/drawable_gl_impl.hpp | 66 ++++- src/mbgl/gl/fence.cpp | 84 ++++++- src/mbgl/gl/fence.hpp | 5 +- src/mbgl/gl/index_buffer_resource.cpp | 23 +- src/mbgl/gl/index_buffer_resource.hpp | 15 +- src/mbgl/gl/layer_group_gl.cpp | 11 +- src/mbgl/gl/renderer_backend.cpp | 12 + src/mbgl/gl/resource_upload_thread_pool.cpp | 57 +++++ src/mbgl/gl/resource_upload_thread_pool.hpp | 19 ++ src/mbgl/gl/texture2d.cpp | 13 +- src/mbgl/gl/upload_pass.cpp | 162 ++++++++++-- src/mbgl/gl/upload_pass.hpp | 1 + src/mbgl/gl/value.cpp | 2 +- src/mbgl/gl/vertex_array.cpp | 5 +- src/mbgl/gl/vertex_buffer_resource.cpp | 22 +- src/mbgl/gl/vertex_buffer_resource.hpp | 18 +- src/mbgl/mtl/layer_group.cpp | 2 +- src/mbgl/mtl/tile_layer_group.cpp | 2 +- src/mbgl/renderer/render_target.cpp | 4 +- src/mbgl/renderer/renderer_impl.cpp | 7 +- src/mbgl/util/thread_pool.cpp | 36 ++- src/mbgl/util/thread_pool.hpp | 50 +++- src/mbgl/vulkan/layer_group.cpp | 2 +- src/mbgl/vulkan/tile_layer_group.cpp | 2 +- test/CMakeLists.txt | 1 + test/gl/async_resources.test.cpp | 119 +++++++++ 47 files changed, 1216 insertions(+), 156 deletions(-) create mode 100644 src/mbgl/gl/buffer_resource.cpp create mode 100644 src/mbgl/gl/buffer_resource.hpp create mode 100644 src/mbgl/gl/resource_upload_thread_pool.cpp create mode 100644 src/mbgl/gl/resource_upload_thread_pool.hpp create mode 100644 test/gl/async_resources.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f65d7691525..08b8906b595 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1079,6 +1079,8 @@ if(MLN_WITH_OPENGL) SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/gl/attribute.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/attribute.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/gl/buffer_resource.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/gl/buffer_resource.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/command_encoder.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/command_encoder.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/context.cpp @@ -1107,6 +1109,8 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/src/mbgl/gl/renderer_backend.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/resource_pool.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/resource_pool.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/gl/resource_upload_thread_pool.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/gl/resource_upload_thread_pool.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/state.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/texture.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/texture.hpp diff --git a/bazel/core.bzl b/bazel/core.bzl index d168dd7e311..fd39c6c6309 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -864,6 +864,8 @@ MLN_CORE_HEADERS = [ MLN_OPENGL_SOURCE = [ "src/mbgl/gl/attribute.cpp", "src/mbgl/gl/attribute.hpp", + "src/mbgl/gl/buffer_resource.cpp", + "src/mbgl/gl/buffer_resource.hpp", "src/mbgl/gl/command_encoder.cpp", "src/mbgl/gl/command_encoder.hpp", "src/mbgl/gl/context.cpp", @@ -892,6 +894,8 @@ MLN_OPENGL_SOURCE = [ "src/mbgl/gl/renderer_backend.cpp", "src/mbgl/gl/resource_pool.cpp", "src/mbgl/gl/resource_pool.hpp", + "src/mbgl/gl/resource_upload_thread_pool.cpp", + "src/mbgl/gl/resource_upload_thread_pool.hpp", "src/mbgl/gl/state.hpp", "src/mbgl/gl/texture.cpp", "src/mbgl/gl/texture.hpp", diff --git a/include/mbgl/gl/drawable_gl.hpp b/include/mbgl/gl/drawable_gl.hpp index 6746939c22d..f0a889a87f7 100644 --- a/include/mbgl/gl/drawable_gl.hpp +++ b/include/mbgl/gl/drawable_gl.hpp @@ -46,7 +46,7 @@ class DrawableGL : public gfx::Drawable { void setVertexAttrId(const size_t id); - void upload(gfx::UploadPass&); + void issueUpload(gfx::UploadPass&); protected: class Impl; diff --git a/include/mbgl/gl/layer_group_gl.hpp b/include/mbgl/gl/layer_group_gl.hpp index 0250a854580..9cf7c4f232b 100644 --- a/include/mbgl/gl/layer_group_gl.hpp +++ b/include/mbgl/gl/layer_group_gl.hpp @@ -17,7 +17,7 @@ class TileLayerGroupGL : public TileLayerGroup { TileLayerGroupGL(int32_t layerIndex, std::size_t initialCapacity, std::string name); ~TileLayerGroupGL() override {} - void upload(gfx::UploadPass&) override; + void issueUpload(gfx::UploadPass&) override; void render(RenderOrchestrator&, PaintParameters&) override; const gfx::UniformBufferArray& getUniformBuffers() const override { return uniformBuffers; }; @@ -39,7 +39,7 @@ class LayerGroupGL : public LayerGroup { LayerGroupGL(int32_t layerIndex, std::size_t initialCapacity, std::string name); ~LayerGroupGL() override {} - void upload(gfx::UploadPass&) override; + void issueUpload(gfx::UploadPass&) override; void render(RenderOrchestrator&, PaintParameters&) override; const gfx::UniformBufferArray& getUniformBuffers() const override { return uniformBuffers; }; diff --git a/include/mbgl/gl/renderer_backend.hpp b/include/mbgl/gl/renderer_backend.hpp index 145c1f78f22..c471790b41a 100644 --- a/include/mbgl/gl/renderer_backend.hpp +++ b/include/mbgl/gl/renderer_backend.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -14,6 +15,16 @@ namespace gl { using ProcAddress = void (*)(); using FramebufferID = uint32_t; +class UploadThreadContext { +public: + UploadThreadContext() = default; + virtual ~UploadThreadContext() = default; + virtual void createContext() = 0; + virtual void destroyContext() = 0; + virtual void bindContext() = 0; + virtual void unbindContext() = 0; +}; + class RendererBackend : public gfx::RendererBackend { public: RendererBackend(gfx::ContextMode); @@ -28,6 +39,11 @@ class RendererBackend : public gfx::RendererBackend { void initShaders(gfx::ShaderRegistry&, const ProgramParameters& programParameters) override; #endif + virtual bool supportFreeThreadedUpload() const { return false; } + virtual void initFreeThreadedUpload() {} + virtual std::shared_ptr createUploadThreadContext() { return nullptr; } + gl::ResourceUploadThreadPool& getResourceUploadThreadPool(); + protected: std::unique_ptr createContext() override; @@ -52,12 +68,17 @@ class RendererBackend : public gfx::RendererBackend { /// Returns true when assumed framebuffer binding hasn't changed from the implicit binding. bool implicitFramebufferBound(); + void destroyResourceUploadThreadPool() { resourceUploadThreadPool = nullptr; } + public: /// Triggers an OpenGL state update if the internal assumed state doesn't /// match the supplied values. void setFramebufferBinding(FramebufferID fbo); void setViewport(int32_t x, int32_t y, const Size&); void setScissorTest(bool); + +private: + std::unique_ptr resourceUploadThreadPool; }; } // namespace gl diff --git a/include/mbgl/mtl/layer_group.hpp b/include/mbgl/mtl/layer_group.hpp index 90f742942c6..69a3c005eae 100644 --- a/include/mbgl/mtl/layer_group.hpp +++ b/include/mbgl/mtl/layer_group.hpp @@ -19,7 +19,7 @@ class LayerGroup : public mbgl::LayerGroup { LayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name); ~LayerGroup() override {} - void upload(gfx::UploadPass&) override; + void issueUpload(gfx::UploadPass&) override; void render(RenderOrchestrator&, PaintParameters&) override; const gfx::UniformBufferArray& getUniformBuffers() const override { return uniformBuffers; }; diff --git a/include/mbgl/mtl/tile_layer_group.hpp b/include/mbgl/mtl/tile_layer_group.hpp index 89b51e804ee..83ff2dc25c4 100644 --- a/include/mbgl/mtl/tile_layer_group.hpp +++ b/include/mbgl/mtl/tile_layer_group.hpp @@ -24,7 +24,7 @@ class TileLayerGroup : public mbgl::TileLayerGroup { TileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name); ~TileLayerGroup() override {} - void upload(gfx::UploadPass&) override; + void issueUpload(gfx::UploadPass&) override; void render(RenderOrchestrator&, PaintParameters&) override; const gfx::UniformBufferArray& getUniformBuffers() const override { return uniformBuffers; }; diff --git a/include/mbgl/renderer/layer_group.hpp b/include/mbgl/renderer/layer_group.hpp index 27b6cdcdedc..7e8f92826d8 100644 --- a/include/mbgl/renderer/layer_group.hpp +++ b/include/mbgl/renderer/layer_group.hpp @@ -89,7 +89,7 @@ class LayerGroupBase : public util::SimpleIdentifiable { /// Called before starting each frame virtual void preRender(RenderOrchestrator&, PaintParameters&) {} /// Called during the upload pass - virtual void upload(gfx::UploadPass&) {} + virtual void issueUpload(gfx::UploadPass&) {} /// Called during each render pass virtual void render(RenderOrchestrator&, PaintParameters&) {} /// Called at the end of each frame diff --git a/include/mbgl/renderer/render_target.hpp b/include/mbgl/renderer/render_target.hpp index e368034af52..6e220db3552 100644 --- a/include/mbgl/renderer/render_target.hpp +++ b/include/mbgl/renderer/render_target.hpp @@ -63,7 +63,7 @@ class RenderTarget { } /// Upload the layer groups - void upload(gfx::UploadPass& uploadPass); + void issueUpload(gfx::UploadPass& uploadPass); /// Render the layer groups void render(RenderOrchestrator&, const RenderTree&, PaintParameters&); diff --git a/include/mbgl/util/string.hpp b/include/mbgl/util/string.hpp index 9ce3abf550d..02232592187 100644 --- a/include/mbgl/util/string.hpp +++ b/include/mbgl/util/string.hpp @@ -92,6 +92,11 @@ inline float stof(const std::string &str) { return std::stof(str); } +// returns true if str contains substr +inline bool contains(const std::string &str, const std::string &substr) { + return str.find(substr) != std::string::npos; +} + } // namespace util } // namespace mbgl diff --git a/include/mbgl/vulkan/layer_group.hpp b/include/mbgl/vulkan/layer_group.hpp index b94779483d8..b18ab98479b 100644 --- a/include/mbgl/vulkan/layer_group.hpp +++ b/include/mbgl/vulkan/layer_group.hpp @@ -19,7 +19,7 @@ class LayerGroup : public mbgl::LayerGroup { LayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name); ~LayerGroup() override {} - void upload(gfx::UploadPass&) override; + void issueUpload(gfx::UploadPass&) override; void render(RenderOrchestrator&, PaintParameters&) override; const gfx::UniformBufferArray& getUniformBuffers() const override { return uniformBuffers; }; diff --git a/include/mbgl/vulkan/tile_layer_group.hpp b/include/mbgl/vulkan/tile_layer_group.hpp index 2b08d7a99f7..d0931563dcf 100644 --- a/include/mbgl/vulkan/tile_layer_group.hpp +++ b/include/mbgl/vulkan/tile_layer_group.hpp @@ -21,7 +21,7 @@ class TileLayerGroup : public mbgl::TileLayerGroup { TileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name); ~TileLayerGroup() override {} - void upload(gfx::UploadPass&) override; + void issueUpload(gfx::UploadPass&) override; void render(RenderOrchestrator&, PaintParameters&) override; const gfx::UniformBufferArray& getUniformBuffers() const override { return uniformBuffers; }; diff --git a/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp b/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp index ebf5df78888..14c92bb7009 100644 --- a/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp @@ -3,13 +3,44 @@ #include #include #include +#include +#include +#include -#include +#include #include +#include +#include namespace mbgl { namespace android { +namespace { + +std::mutex& getEglMutex() { + static std::mutex eglMutex; + return eglMutex; +} + +std::string androidSysProp(const char* key) { + assert(strlen(key) < PROP_NAME_MAX); + if (__system_property_find(key) == nullptr) { + return ""; + } + char prop[PROP_VALUE_MAX + 1]; + __system_property_get(key, prop); + return prop; +} + +bool inEmulator() { + return androidSysProp("ro.kernel.qemu") == "1" || androidSysProp("ro.boot.qemu") == "1" || + androidSysProp("ro.hardware.egl") == "emulation" || + util::contains(androidSysProp("ro.build.fingerprint"), "emu") || + util::contains(androidSysProp("ro.build.product"), "emu") || + util::contains(androidSysProp("ro.product.device"), "emu"); +} + +} // namespace class AndroidGLRenderableResource final : public mbgl::gl::RenderableResource { public: @@ -37,7 +68,13 @@ AndroidGLRendererBackend::AndroidGLRendererBackend() : gl::RendererBackend(gfx::ContextMode::Unique), mbgl::gfx::Renderable({64, 64}, std::make_unique(*this)) {} -AndroidGLRendererBackend::~AndroidGLRendererBackend() = default; +AndroidGLRendererBackend::~AndroidGLRendererBackend() { + if (eglWaitClient() == EGL_FALSE) { + auto err = "eglWaitClient failed. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + } + destroyResourceUploadThreadPool(); +} gl::ProcAddress AndroidGLRendererBackend::getExtensionFunctionPointer(const char* name) { assert(gfx::BackendScope::exists()); @@ -69,6 +106,203 @@ void AndroidGLRendererBackend::markContextLost() { } } +bool AndroidGLRendererBackend::supportFreeThreadedUpload() const { + // Android emulator does not support shared EGL contexts + static bool isInEmu = inEmulator(); + return MLN_ANDROID_RENDER_BACKEND_SHARED_EGL_CONTEXTS && !isInEmu; +} + +std::shared_ptr AndroidGLRendererBackend::createUploadThreadContext() { + MLN_TRACE_FUNC(); + + assert(eglMainCtx != EGL_NO_CONTEXT); + return std::make_shared(*this, eglDsply, eglConfig, eglMainCtx, eglClientVersion); +} + +void AndroidGLRendererBackend::initFreeThreadedUpload() { + MLN_TRACE_FUNC(); + + if (eglMainCtx != EGL_NO_CONTEXT) { + // Already initialized + return; + } + assert(eglDsply == EGL_NO_DISPLAY); + assert(eglSurf == EGL_NO_SURFACE); + assert(eglGetError() == EGL_SUCCESS); + + eglMainCtx = eglGetCurrentContext(); + if (eglMainCtx == EGL_NO_CONTEXT) { + constexpr const char* err = "eglGetCurrentContext returned EGL_NO_CONTEXT"; + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + + eglDsply = eglGetCurrentDisplay(); + if (eglDsply == EGL_NO_DISPLAY) { + constexpr const char* err = "eglGetCurrentDisplay returned EGL_NO_DISPLAY"; + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + + eglSurf = eglGetCurrentSurface(EGL_READ); + if (eglSurf == EGL_NO_SURFACE) { + constexpr const char* err = "eglGetCurrentSurface returned EGL_NO_SURFACE"; + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + + EGLSurface writeSurf = eglGetCurrentSurface(EGL_DRAW); + if (eglSurf != writeSurf) { + constexpr const char* err = "EGL_READ and EGL_DRAW surfaces are different"; + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + + int config_id = 0; + if (eglQueryContext(eglDsply, eglMainCtx, EGL_CONFIG_ID, &config_id) == EGL_FALSE) { + auto err = "eglQueryContext for EGL_CONFIG_ID failed. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + + int config_count = 0; + const EGLint attribs[] = {EGL_CONFIG_ID, config_id, EGL_NONE}; + if (eglChooseConfig(eglDsply, attribs, nullptr, 0, &config_count) == EGL_FALSE) { + auto err = "eglChooseConfig failed to query config_count. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + if (config_count != 1) { + auto err = "eglChooseConfig returned multiple configs: " + std::to_string(config_count); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + + if (eglChooseConfig(eglDsply, attribs, &eglConfig, 1, &config_count) == EGL_FALSE) { + auto err = "eglChooseConfig failed to query config. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + + if (eglQueryContext(eglDsply, eglMainCtx, EGL_CONTEXT_CLIENT_VERSION, &eglClientVersion) == EGL_FALSE) { + auto err = "eglQueryContext for client version failed. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } +} + +AndroidUploadThreadContext::AndroidUploadThreadContext(AndroidRendererBackend& backend_, + EGLDisplay display_, + EGLConfig config_, + EGLContext mainContext_, + int clientVersion_) + : backend(backend_), + display(display_), + config(config_), + mainContext(mainContext_), + clientVersion(clientVersion_) {} + +AndroidUploadThreadContext::~AndroidUploadThreadContext() { + MLN_TRACE_FUNC(); + + auto ctx = eglGetCurrentContext(); + if (ctx == EGL_NO_CONTEXT) { + return; // Upload thread clean from any EGL context + } + + if (ctx == sharedContext) { + mbgl::Log::Error(mbgl::Event::OpenGL, "AndroidUploadThreadContext::destroyContext() must be explicitly called"); + } else { + mbgl::Log::Error(mbgl::Event::OpenGL, "Unexpected context bound to an Upload thread"); + } + assert(ctx == EGL_NO_CONTEXT); +} + +void AndroidUploadThreadContext::createContext() { + MLN_TRACE_FUNC(); + + const std::lock_guard lock(getEglMutex()); + + assert(display != EGL_NO_DISPLAY); + assert(mainContext != EGL_NO_CONTEXT); + assert(sharedContext == EGL_NO_CONTEXT); + assert(surface == EGL_NO_SURFACE); + + int attribs[] = {EGL_CONTEXT_CLIENT_VERSION, clientVersion, EGL_NONE}; + sharedContext = eglCreateContext(display, config, mainContext, attribs); + if (sharedContext == EGL_NO_CONTEXT) { + auto err = "eglCreateContext returned EGL_NO_CONTEXT. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + + surface = eglCreatePbufferSurface(display, config, nullptr); + if (surface == EGL_NO_SURFACE) { + auto err = "eglCreatePbufferSurface failed. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + + if (eglMakeCurrent(display, surface, surface, sharedContext) == EGL_FALSE) { + auto err = "eglMakeCurrent for shared context failed. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + throw std::runtime_error(err); + } + MLN_TRACE_GL_CONTEXT(); +} + +void AndroidUploadThreadContext::destroyContext() { + MLN_TRACE_FUNC(); + + const std::lock_guard lock(getEglMutex()); + + if (eglWaitClient() == EGL_FALSE) { + auto err = "eglWaitClient failed. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + } + + auto ctx = eglGetCurrentContext(); + if (ctx == EGL_NO_CONTEXT) { + constexpr const char* err = + "AndroidUploadThreadContext::destroyContext() expects a persistently bound EGL shared context"; + mbgl::Log::Error(mbgl::Event::OpenGL, err); + } else if (ctx != sharedContext) { + constexpr const char* err = + "AndroidUploadThreadContext::destroyContext(): expects a single EGL context to be used in each Upload " + "thread"; + mbgl::Log::Error(mbgl::Event::OpenGL, err); + } + assert(ctx == sharedContext); + + if (eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) == EGL_FALSE) { + auto err = "eglMakeCurrent with EGL_NO_CONTEXT failed. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + } + if (eglDestroyContext(display, sharedContext) == EGL_FALSE) { + auto err = "eglDestroyContext failed. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + } + if (eglDestroySurface(display, surface) == EGL_FALSE) { + auto err = "eglDestroySurface failed. Error code " + std::to_string(eglGetError()); + mbgl::Log::Error(mbgl::Event::OpenGL, err); + } + + display = EGL_NO_DISPLAY; + mainContext = EGL_NO_CONTEXT; + sharedContext = EGL_NO_CONTEXT; + surface = EGL_NO_SURFACE; +} + +void AndroidUploadThreadContext::bindContext() { + // Expect a persistently bound EGL shared context + assert(eglGetCurrentContext() == sharedContext && sharedContext != EGL_NO_CONTEXT); +} + +void AndroidUploadThreadContext::unbindContext() { + // Expect a persistently bound EGL shared context + assert(eglGetCurrentContext() == sharedContext && sharedContext != EGL_NO_CONTEXT); +} + } // namespace android } // namespace mbgl diff --git a/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.hpp b/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.hpp index c27755cc7c8..0e6ef8e476d 100644 --- a/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.hpp @@ -4,6 +4,14 @@ #include #include "android_renderer_backend.hpp" +#include + +#ifdef MLN_ANDROID_RENDER_BACKEND_DISABLE_SHARED_EGL_CONTEXTS +#define MLN_ANDROID_RENDER_BACKEND_SHARED_EGL_CONTEXTS false +#else +#define MLN_ANDROID_RENDER_BACKEND_SHARED_EGL_CONTEXTS true +#endif + namespace mbgl { namespace android { @@ -24,6 +32,10 @@ class AndroidGLRendererBackend : public AndroidRendererBackend, void resizeFramebuffer(int width, int height) override; PremultipliedImage readFramebuffer() override; + bool supportFreeThreadedUpload() const override; + std::shared_ptr createUploadThreadContext() override; + void initFreeThreadedUpload() override; + // mbgl::gfx::RendererBackend implementation public: mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; } @@ -40,6 +52,32 @@ class AndroidGLRendererBackend : public AndroidRendererBackend, protected: mbgl::gl::ProcAddress getExtensionFunctionPointer(const char*) override; void updateAssumedState() override; + +private: + int eglClientVersion = 0; + EGLContext eglMainCtx = EGL_NO_CONTEXT; + EGLDisplay eglDsply = EGL_NO_DISPLAY; + EGLSurface eglSurf = EGL_NO_SURFACE; + EGLConfig eglConfig; +}; + +class AndroidUploadThreadContext : public gl::UploadThreadContext { +public: + AndroidUploadThreadContext(AndroidRendererBackend&, EGLDisplay, EGLConfig, EGLContext, int); + ~AndroidUploadThreadContext() override; + void createContext() override; + void destroyContext() override; + void bindContext() override; + void unbindContext() override; + +private: + AndroidRendererBackend& backend; + EGLDisplay display = EGL_NO_DISPLAY; + EGLConfig config; + EGLContext mainContext = EGL_NO_CONTEXT; + EGLContext sharedContext = EGL_NO_CONTEXT; + EGLSurface surface = EGL_NO_SURFACE; + int clientVersion = 0; }; } // namespace android diff --git a/platform/default/include/mbgl/gl/headless_backend.hpp b/platform/default/include/mbgl/gl/headless_backend.hpp index 85ca7c5cb7b..26965ba55c9 100644 --- a/platform/default/include/mbgl/gl/headless_backend.hpp +++ b/platform/default/include/mbgl/gl/headless_backend.hpp @@ -8,7 +8,7 @@ namespace mbgl { namespace gl { -class HeadlessBackend final : public gl::RendererBackend, public gfx::HeadlessBackend { +class HeadlessBackend : public gl::RendererBackend, public gfx::HeadlessBackend { public: HeadlessBackend(Size = {256, 256}, SwapBehaviour = SwapBehaviour::NoFlush, diff --git a/src/mbgl/gfx/attribute.hpp b/src/mbgl/gfx/attribute.hpp index 2ff1ef573da..8b7ec4613bb 100644 --- a/src/mbgl/gfx/attribute.hpp +++ b/src/mbgl/gfx/attribute.hpp @@ -117,7 +117,7 @@ class AttributeBinding { public: AttributeDescriptor attribute; uint32_t vertexStride; - const VertexBufferResource* vertexBufferResource; + VertexBufferResource* vertexBufferResource; uint32_t vertexOffset; friend bool operator==(const AttributeBinding& lhs, const AttributeBinding& rhs) { diff --git a/src/mbgl/gl/buffer_resource.cpp b/src/mbgl/gl/buffer_resource.cpp new file mode 100644 index 00000000000..38198d58c50 --- /dev/null +++ b/src/mbgl/gl/buffer_resource.cpp @@ -0,0 +1,156 @@ +#include +#include +#include + +#include +#include + +namespace mbgl { +namespace gl { + +using namespace platform; + +BufferResource::BufferResource(UniqueBuffer&& buffer_, int byteSize_) + : buffer(std::make_unique(std::move(buffer_))), + byteSize(byteSize_) { + assert(buffer); +} + +BufferResource::BufferResource(AsyncAllocCallback asyncAllocCallback_, AsyncUpdateCallback asyncUpdateCallback_) + : asyncAllocCallback(std::move(asyncAllocCallback_)), + asyncUpdateCallback(std::move(asyncUpdateCallback_)) { + assert(asyncAllocCallback); +} + +BufferResource::~BufferResource() noexcept { + // We expect the resource to own a buffer at destruction time + assert(buffer); + // No pending async upload + assert(asyncUploadRequested == false); + assert(asyncUploadCommands.data.empty()); +} + +const UniqueBuffer& BufferResource::pickBuffer() const { + // wait must be called first + assert(asyncUploadRequested == false); + assert(asyncUploadIssued == false); + // BufferResource must own a buffer + assert(buffer); + return *buffer; +} + +const UniqueBuffer& BufferResource::waitAndGetBuffer() { + MLN_TRACE_FUNC(); + + wait(); + return pickBuffer(); +} + +void BufferResource::asyncAlloc(ResourceUploadThreadPool& threadPool, + int size, + gfx::BufferUsageType usage, + const void* initialData) { + MLN_TRACE_FUNC(); + + assert(asyncAllocCallback); + // BufferResource must not own a buffer yet since we are allocating one + assert(!buffer); + // BufferResource must not have a pending async upload + assert(asyncUploadIssued == false); + assert(asyncUploadRequested == false); + asyncUploadRequested = true; + + auto& cmd = asyncUploadCommands; + assert(cmd.data.empty()); + assert(cmd.type == BufferAsyncUploadCommandType::None); + assert(byteSize == 0); + byteSize = size; + cmd.type = BufferAsyncUploadCommandType::Alloc; + cmd.dataSize = size; + cmd.usage = usage; + cmd.data.resize((size + sizeof(uint64_t) - 1) / sizeof(uint64_t)); + std::memcpy(cmd.data.data(), initialData, size); + + threadPool.schedule([this] { issueAsyncUpload(); }); +} + +void BufferResource::asyncUpdate(ResourceUploadThreadPool& threadPool, int size, const void* data) { + MLN_TRACE_FUNC(); + + assert(asyncUpdateCallback); + // BufferResource must own a buffer to update it + assert(buffer); + // BufferResource must not have a pending async upload + assert(asyncUploadIssued == false); + assert(asyncUploadRequested == false); + asyncUploadRequested = true; + + auto& cmd = asyncUploadCommands; + assert(cmd.data.empty()); + assert(cmd.type == BufferAsyncUploadCommandType::None); + assert(size <= byteSize); + byteSize = size; + cmd.type = BufferAsyncUploadCommandType::Update; + cmd.dataSize = size; + cmd.data.resize((size + sizeof(uint64_t) - 1) / sizeof(uint64_t)); + std::memcpy(cmd.data.data(), data, size); + + threadPool.schedule([this] { issueAsyncUpload(); }); +} + +void BufferResource::wait() { + MLN_TRACE_FUNC(); + + std::unique_lock lk(asyncUploadMutex); + if (!asyncUploadRequested) { + return; + } + if (!asyncUploadIssued) { + asyncUploadConditionVar.wait(lk, [&] { return asyncUploadIssued; }); + } + assert(asyncUploadCommands.data.empty()); + assert(asyncUploadCommands.type == BufferAsyncUploadCommandType::None); +#ifdef MLN_RENDER_BACKEND_USE_UPLOAD_GL_FENCE + gpuFence.gpuWait(); + gpuFence.reset(); +#endif + asyncUploadIssued = false; + asyncUploadRequested = false; +} + +void BufferResource::issueAsyncUpload() { + MLN_TRACE_FUNC(); + + const std::lock_guard lk(asyncUploadMutex); + + assert(asyncUploadRequested == true); + assert(asyncUploadIssued == false); + + auto& cmd = asyncUploadCommands; + switch (cmd.type) { + case BufferAsyncUploadCommandType::Alloc: + buffer = std::make_unique(asyncAllocCallback(cmd.dataSize, cmd.usage, cmd.data.data())); + break; + case BufferAsyncUploadCommandType::Update: + asyncUpdateCallback(*buffer, cmd.dataSize, cmd.data.data()); + break; + default: + assert(false); + break; + } +#ifdef MLN_RENDER_BACKEND_USE_UPLOAD_GL_FENCE + gpuFence.insert(); +#else + MBGL_CHECK_ERROR(glFlush()); +#endif + + cmd.type = BufferAsyncUploadCommandType::None; + cmd.dataSize = 0; + cmd.data.clear(); + asyncUploadIssued = true; + + asyncUploadConditionVar.notify_all(); +} + +} // namespace gl +} // namespace mbgl \ No newline at end of file diff --git a/src/mbgl/gl/buffer_resource.hpp b/src/mbgl/gl/buffer_resource.hpp new file mode 100644 index 00000000000..0553bdf2ea4 --- /dev/null +++ b/src/mbgl/gl/buffer_resource.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mbgl { +namespace gl { + +class BufferResource { +public: + using AsyncAllocCallback = std::function; + using AsyncUpdateCallback = std::function; + + // Create a vertex buffer resource that takes ownership of buffer_ + BufferResource(UniqueBuffer&& buffer_, int byteSize_); + + // Create a non-owning vertex buffer resource + BufferResource(AsyncAllocCallback, AsyncUpdateCallback); + + virtual ~BufferResource() noexcept; + + int getByteSize() const { return byteSize; } + + // Access the buffer + // wait() must be called before pickBuffer() if the buffer is pending an async upload + const UniqueBuffer& pickBuffer() const; + + // Access the buffer. First call wait() if the buffer is pending an async upload + const UniqueBuffer& waitAndGetBuffer(); + + // Issue an alloc or update command asynchronously to process in the upload pass + void asyncAlloc(ResourceUploadThreadPool& threadPool, + int size, + gfx::BufferUsageType usage, + const void* initialData = nullptr); + void asyncUpdate(ResourceUploadThreadPool& threadPool, int size, const void* data); + + // Wait for the async upload to complete + void wait(); + + bool isAsyncPending() const { return asyncUploadRequested; } + +private: + enum class BufferAsyncUploadCommandType : uint8_t { + Alloc, + Update, + None, + }; + + struct BufferAsyncUploadCommand { + std::vector data; // only dataSize bytes are valid within data + int dataSize = 0; + gfx::BufferUsageType usage; + BufferAsyncUploadCommandType type = BufferAsyncUploadCommandType::None; + }; + + void issueAsyncUpload(); + +protected: + std::unique_ptr buffer = nullptr; + int byteSize = 0; + + BufferAsyncUploadCommand asyncUploadCommands; + AsyncAllocCallback asyncAllocCallback; + AsyncUpdateCallback asyncUpdateCallback; + Fence gpuFence; + bool asyncUploadRequested = false; + bool asyncUploadIssued = false; + std::condition_variable asyncUploadConditionVar; + std::mutex asyncUploadMutex; +}; + +} // namespace gl +} // namespace mbgl diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index 03216eaa3d2..a8d7c5f6541 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -793,6 +793,13 @@ void Context::finish() { MBGL_CHECK_ERROR(glFinish()); } +void Context::flushCommands() { + MLN_TRACE_FUNC(); + MLN_TRACE_FUNC_GL(); + + MBGL_CHECK_ERROR(glFlush()); +} + #if MLN_DRAWABLE_RENDERER std::shared_ptr Context::getCurrentFrameFence() const { return frameInFlightFence; diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index ae3b50cf84b..94da219b139 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -87,8 +87,12 @@ class Context final : public gfx::Context { void draw(const gfx::DrawMode&, std::size_t indexOffset, std::size_t indexLength); + // Flush the OpenGL command queue and wait for commands to finish executing. void finish(); + // Flush the OpenGL command queue without waiting for commands to finish executing. + void flushCommands(); + #if MLN_DRAWABLE_RENDERER std::shared_ptr getCurrentFrameFence() const; #endif @@ -160,6 +164,8 @@ class Context final : public gfx::Context { Texture2DPool& getTexturePool(); + RendererBackend& getBackend() { return backend; } + private: RendererBackend& backend; bool cleanupOnDestruction = true; diff --git a/src/mbgl/gl/drawable_gl.cpp b/src/mbgl/gl/drawable_gl.cpp index b034b4f8e7c..3b582173537 100644 --- a/src/mbgl/gl/drawable_gl.cpp +++ b/src/mbgl/gl/drawable_gl.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -31,6 +32,8 @@ void DrawableGL::draw(PaintParameters& parameters) const { auto& context = static_cast(parameters.context); + impl->createVAOs(context); + if (shader) { const auto& shaderGL = static_cast(*shader); if (shaderGL.getGLProgramID() != context.program.getCurrentValue()) { @@ -132,15 +135,7 @@ void DrawableGL::unbindUniformBuffers() const { } } -struct IndexBufferGL : public gfx::IndexBufferBase { - IndexBufferGL(std::unique_ptr&& buffer_) - : buffer(std::move(buffer_)) {} - ~IndexBufferGL() override = default; - - std::unique_ptr buffer; -}; - -void DrawableGL::upload(gfx::UploadPass& uploadPass) { +void DrawableGL::issueUpload(gfx::UploadPass& uploadPass) { if (isCustom) { return; } @@ -151,16 +146,9 @@ void DrawableGL::upload(gfx::UploadPass& uploadPass) { } MLN_TRACE_FUNC(); -#ifdef MLN_TRACY_ENABLE - { - auto str = name + "/" + (tileID ? util::toString(*tileID) : std::string()); - MLN_ZONE_STR(str); - } -#endif - auto& context = uploadPass.getContext(); - auto& glContext = static_cast(context); constexpr auto usage = gfx::BufferUsageType::StaticDraw; + assert(impl); // Create an index buffer if necessary if (impl->indexes) { @@ -202,33 +190,6 @@ void DrawableGL::upload(gfx::UploadPass& uploadPass) { impl->attributeBuffers = std::move(vertexBuffers); } - // Bind a VAO for each group of vertexes described by a segment - for (const auto& seg : impl->segments) { - MLN_TRACE_ZONE(segment); - auto& glSeg = static_cast(*seg); - const auto& mlSeg = glSeg.getSegment(); - - if (mlSeg.indexLength == 0) { - continue; - } - - for (auto& binding : impl->attributeBindings) { - if (binding) { - binding->vertexOffset = static_cast(mlSeg.vertexOffset); - } - } - - if (!glSeg.getVertexArray().isValid() && impl->indexes) { - auto vertexArray = glContext.createVertexArray(); - const auto& indexBuffer = static_cast(*impl->indexes->getBuffer()); - vertexArray.bind(glContext, *indexBuffer.buffer, impl->attributeBindings); - assert(vertexArray.isValid()); - if (vertexArray.isValid()) { - glSeg.setVertexArray(std::move(vertexArray)); - } - } - } - const auto needsUpload = [](const auto& texture) { return texture && texture->needsUpload(); }; @@ -255,6 +216,7 @@ gfx::StencilMode DrawableGL::makeStencilMode(PaintParameters& parameters) const void DrawableGL::uploadTextures() const { MLN_TRACE_FUNC(); + for (const auto& texture : textures) { if (texture) { texture->upload(); diff --git a/src/mbgl/gl/drawable_gl_impl.hpp b/src/mbgl/gl/drawable_gl_impl.hpp index 37bf3f8a31b..132c5da550a 100644 --- a/src/mbgl/gl/drawable_gl_impl.hpp +++ b/src/mbgl/gl/drawable_gl_impl.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,28 @@ namespace gl { using namespace platform; +struct DrawableGL::DrawSegmentGL final : public gfx::Drawable::DrawSegment { + DrawSegmentGL(gfx::DrawMode mode_, SegmentBase&& segment_, VertexArray&& vertexArray_) + : gfx::Drawable::DrawSegment(mode_, std::move(segment_)), + vertexArray(std::move(vertexArray_)) {} + + ~DrawSegmentGL() override = default; + + const VertexArray& getVertexArray() const { return vertexArray; } + void setVertexArray(VertexArray&& value) { vertexArray = std::move(value); } + +protected: + VertexArray vertexArray; +}; + +struct IndexBufferGL : public gfx::IndexBufferBase { + IndexBufferGL(std::unique_ptr&& buffer_) + : buffer(std::move(buffer_)) {} + ~IndexBufferGL() override = default; + + std::unique_ptr buffer; +}; + class DrawableGL::Impl final { public: Impl() = default; @@ -51,20 +74,37 @@ class DrawableGL::Impl final { GLfloat pointSize = 0.0f; size_t vertexAttrId = 0; -}; - -struct DrawableGL::DrawSegmentGL final : public gfx::Drawable::DrawSegment { - DrawSegmentGL(gfx::DrawMode mode_, SegmentBase&& segment_, VertexArray&& vertexArray_) - : gfx::Drawable::DrawSegment(mode_, std::move(segment_)), - vertexArray(std::move(vertexArray_)) {} - ~DrawSegmentGL() override = default; - - const VertexArray& getVertexArray() const { return vertexArray; } - void setVertexArray(VertexArray&& value) { vertexArray = std::move(value); } - -protected: - VertexArray vertexArray; + void createVAOs(gl::Context& context) { + MLN_TRACE_FUNC(); + + // Bind a VAO for each group of vertexes described by a segment + for (const auto& seg : segments) { + MLN_TRACE_ZONE(VAO_For_segment); + auto& glSeg = static_cast(*seg); + const auto& mlSeg = glSeg.getSegment(); + + if (mlSeg.indexLength == 0) { + continue; + } + + for (auto& binding : attributeBindings) { + if (binding) { + binding->vertexOffset = static_cast(mlSeg.vertexOffset); + } + } + + if (!glSeg.getVertexArray().isValid() && indexes) { + auto vertexArray = context.createVertexArray(); + const auto& indexBuf = static_cast(*indexes->getBuffer()); + vertexArray.bind(context, *indexBuf.buffer, attributeBindings); + assert(vertexArray.isValid()); + if (vertexArray.isValid()) { + glSeg.setVertexArray(std::move(vertexArray)); + } + } + } + } }; } // namespace gl diff --git a/src/mbgl/gl/fence.cpp b/src/mbgl/gl/fence.cpp index 350939e6cf8..c8f06c7006c 100644 --- a/src/mbgl/gl/fence.cpp +++ b/src/mbgl/gl/fence.cpp @@ -1,6 +1,8 @@ #include #include #include +#include + #include #include #include @@ -26,18 +28,19 @@ std::string glErrors() { Fence::Fence() {} Fence::~Fence() { - MLN_TRACE_FUNC(); - - if (fence) { - glDeleteSync(fence); - } + reset(); } -void Fence::insert() noexcept { +void Fence::insert() { MLN_TRACE_FUNC(); assert(!fence); fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + if (!fence) { + auto err = "glFenceSync failed. " + glErrors(); + Log::Error(Event::OpenGL, err); + throw std::runtime_error(err); + } } bool Fence::isSignaled() const { @@ -56,14 +59,79 @@ bool Fence::isSignaled() const { return true; case GL_TIMEOUT_EXPIRED: return false; - case GL_WAIT_FAILED: - throw std::runtime_error("glClientWaitSync failed. " + glErrors()); + case GL_WAIT_FAILED: { + auto err = "glClientWaitSync failed. " + glErrors(); + Log::Error(Event::OpenGL, err); + throw std::runtime_error(err); return false; + } default: assert(false); // unreachable return false; } } +void Fence::cpuWait() const { + MLN_TRACE_FUNC(); + + if (!fence) { + constexpr const char* err = "A fence must be inserted in command stream before waiting for it to get signaled"; + Log::Error(Event::OpenGL, err); + throw std::runtime_error(err); + } + + constexpr GLuint64 oneSecInNanoSec = 1000000000; // 1 second + constexpr GLuint64 timeout = oneSecInNanoSec; // 1 second + constexpr int retryCount = 10; // 10 seconds + + for (int i = 0; i < retryCount; ++i) { + GLenum fenceStatus = glClientWaitSync(fence, GL_SYNC_FLUSH_COMMANDS_BIT, timeout); + switch (fenceStatus) { + case GL_ALREADY_SIGNALED: + [[fallthrough]]; + case GL_CONDITION_SATISFIED: + return; + case GL_TIMEOUT_EXPIRED: + Log::Error( + Event::OpenGL, + "glClientWaitSync timeout after " + std::to_string(i * timeout / oneSecInNanoSec) + " seconds"); + break; + case GL_WAIT_FAILED: { + auto err = "glClientWaitSync failed. " + glErrors(); + Log::Error(Event::OpenGL, err); + throw std::runtime_error(err); + return; + } + default: + assert(false); // unreachable + return; + } + } + + throw std::runtime_error("GPU unresponsive after. " + std::to_string(retryCount * timeout / oneSecInNanoSec) + + " seconds"); +} + +void Fence::gpuWait() const { + MLN_TRACE_FUNC(); + + if (!fence) { + constexpr const char* err = "A fence must be inserted in command stream before waiting for it to get signaled"; + Log::Error(Event::OpenGL, err); + throw std::runtime_error(err); + } + + MBGL_CHECK_ERROR(glWaitSync(fence, 0, GL_TIMEOUT_IGNORED)); +} + +void Fence::reset() noexcept { + MLN_TRACE_FUNC(); + + if (fence) { + glDeleteSync(fence); + fence = nullptr; + } +} + } // namespace gl } // namespace mbgl diff --git a/src/mbgl/gl/fence.hpp b/src/mbgl/gl/fence.hpp index 9edb413caa9..8b703a385f3 100644 --- a/src/mbgl/gl/fence.hpp +++ b/src/mbgl/gl/fence.hpp @@ -11,8 +11,11 @@ class Fence { Fence(); ~Fence(); - void insert() noexcept; + void insert(); bool isSignaled() const; + void cpuWait() const; + void gpuWait() const; + void reset() noexcept; private: platform::GLsync fence{nullptr}; diff --git a/src/mbgl/gl/index_buffer_resource.cpp b/src/mbgl/gl/index_buffer_resource.cpp index c5b558ecde4..1f6036146e1 100644 --- a/src/mbgl/gl/index_buffer_resource.cpp +++ b/src/mbgl/gl/index_buffer_resource.cpp @@ -2,18 +2,31 @@ #include #include +#include + namespace mbgl { namespace gl { IndexBufferResource::IndexBufferResource(UniqueBuffer&& buffer_, int byteSize_) - : buffer(std::move(buffer_)), - byteSize(byteSize_) { - MLN_TRACE_ALLOC_INDEX_BUFFER(buffer.get(), byteSize); + : BufferResource(std::move(buffer_), byteSize_) { + MLN_TRACE_ALLOC_INDEX_BUFFER(buffer->get(), byteSize); +} + +IndexBufferResource::IndexBufferResource(AsyncAllocCallback alloc, AsyncUpdateCallback update, int byteSize_) + : BufferResource(std::move(alloc), std::move(update)) { + (void)byteSize_; // Unused if Tracy is not enabled + MLN_TRACE_ALLOC_INDEX_BUFFER(buffer->get(), byteSize_); } IndexBufferResource::~IndexBufferResource() noexcept { - MLN_TRACE_FREE_INDEX_BUFFER(buffer.get()); - auto& stats = buffer.get_deleter().context.renderingStats(); + MLN_TRACE_FUNC(); + + // Must wait before destruction in case a resource is created but never used (wait never called) + wait(); + + auto& underlyingBuffer = *buffer; + MLN_TRACE_FREE_INDEX_BUFFER(underlyingBuffer.get()); + auto& stats = underlyingBuffer.get_deleter().context.renderingStats(); stats.memIndexBuffers -= byteSize; assert(stats.memIndexBuffers >= 0); } diff --git a/src/mbgl/gl/index_buffer_resource.hpp b/src/mbgl/gl/index_buffer_resource.hpp index 5cbb202392a..468bf1c9eff 100644 --- a/src/mbgl/gl/index_buffer_resource.hpp +++ b/src/mbgl/gl/index_buffer_resource.hpp @@ -1,18 +1,23 @@ #pragma once #include -#include +#include namespace mbgl { namespace gl { -class IndexBufferResource : public gfx::IndexBufferResource { +class IndexBufferResource : public BufferResource, public gfx::IndexBufferResource { public: + using BufferResource::AsyncAllocCallback; + using BufferResource::AsyncUpdateCallback; + + // Create a buffer resource that takes ownership of buffer_ IndexBufferResource(UniqueBuffer&& buffer_, int byteSize_); - ~IndexBufferResource() noexcept override; - UniqueBuffer buffer; - int byteSize; + // Create a non-owning buffer resource + IndexBufferResource(AsyncAllocCallback alloc, AsyncUpdateCallback update, int byteSize_); + + ~IndexBufferResource() noexcept override; }; } // namespace gl diff --git a/src/mbgl/gl/layer_group_gl.cpp b/src/mbgl/gl/layer_group_gl.cpp index 9e4ada2ec66..320be028f8e 100644 --- a/src/mbgl/gl/layer_group_gl.cpp +++ b/src/mbgl/gl/layer_group_gl.cpp @@ -20,10 +20,7 @@ using namespace platform; TileLayerGroupGL::TileLayerGroupGL(int32_t layerIndex_, std::size_t initialCapacity, std::string name_) : TileLayerGroup(layerIndex_, initialCapacity, std::move(name_)) {} -void TileLayerGroupGL::upload(gfx::UploadPass& uploadPass) { - MLN_TRACE_FUNC(); - MLN_ZONE_STR(name); - +void TileLayerGroupGL::issueUpload(gfx::UploadPass& uploadPass) { if (!enabled) { return; } @@ -44,7 +41,7 @@ void TileLayerGroupGL::upload(gfx::UploadPass& uploadPass) { const auto debugGroup = uploadPass.createDebugGroup(labelPtr); #endif - drawableGL.upload(uploadPass); + drawableGL.issueUpload(uploadPass); }); } @@ -167,7 +164,7 @@ void TileLayerGroupGL::unbindUniformBuffers() const { LayerGroupGL::LayerGroupGL(int32_t layerIndex_, std::size_t initialCapacity, std::string name_) : LayerGroup(layerIndex_, initialCapacity, std::move(name_)) {} -void LayerGroupGL::upload(gfx::UploadPass& uploadPass) { +void LayerGroupGL::issueUpload(gfx::UploadPass& uploadPass) { if (!enabled) { return; } @@ -183,7 +180,7 @@ void LayerGroupGL::upload(gfx::UploadPass& uploadPass) { const auto debugGroup = uploadPass.createDebugGroup(drawable.getName().c_str()); #endif - drawableGL.upload(uploadPass); + drawableGL.issueUpload(uploadPass); }); } diff --git a/src/mbgl/gl/renderer_backend.cpp b/src/mbgl/gl/renderer_backend.cpp index fa8badc2d6e..7e0036770a9 100644 --- a/src/mbgl/gl/renderer_backend.cpp +++ b/src/mbgl/gl/renderer_backend.cpp @@ -12,6 +12,7 @@ #endif #include +#include namespace mbgl { namespace gl { @@ -149,5 +150,16 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar } #endif +gl::ResourceUploadThreadPool& RendererBackend::getResourceUploadThreadPool() { + if (!supportFreeThreadedUpload()) { + throw std::runtime_error("Parallel resource upload is not supported on this backend"); + } + if (!resourceUploadThreadPool) { + initFreeThreadedUpload(); + resourceUploadThreadPool = std::make_unique(*this); + } + return *resourceUploadThreadPool; +} + } // namespace gl } // namespace mbgl diff --git a/src/mbgl/gl/resource_upload_thread_pool.cpp b/src/mbgl/gl/resource_upload_thread_pool.cpp new file mode 100644 index 00000000000..0693f2399e3 --- /dev/null +++ b/src/mbgl/gl/resource_upload_thread_pool.cpp @@ -0,0 +1,57 @@ +#include + +#include + +#include +#include + +namespace mbgl { +namespace gl { + +namespace { + +size_t numberOfResourceUploadThreads() { + size_t hwThreads = std::thread::hardware_concurrency(); + if (hwThreads < 2) { + return 1; + } + // Set the pool size to the number of threads minus one to account for the main render thread + return std::thread::hardware_concurrency() - 1; +} + +std::function makeThreadCallbacks(RendererBackend& backend) { + assert(backend.supportFreeThreadedUpload()); + + // callbacks will run in a separate thread. It is assumed the backend exists during upload + + auto callbackGen = [&]() { + auto ctx = backend.createUploadThreadContext(); + ThreadCallbacks callbacks; + callbacks.onThreadBegin = [ctx = ctx]() { + ctx->createContext(); + }; + + callbacks.onThreadEnd = [ctx = ctx]() { + ctx->destroyContext(); + }; + + callbacks.onTaskBegin = [ctx = ctx]() { + ctx->bindContext(); + }; + + callbacks.onTaskEnd = [ctx = ctx]() { + ctx->unbindContext(); + }; + return callbacks; + }; + + return callbackGen; +} + +} // namespace + +ResourceUploadThreadPool::ResourceUploadThreadPool(RendererBackend& backend) + : ThreadedScheduler(numberOfResourceUploadThreads(), false, makeThreadCallbacks(backend), "UploadGL") {} + +} // namespace gl +} // namespace mbgl diff --git a/src/mbgl/gl/resource_upload_thread_pool.hpp b/src/mbgl/gl/resource_upload_thread_pool.hpp new file mode 100644 index 00000000000..a5df4378000 --- /dev/null +++ b/src/mbgl/gl/resource_upload_thread_pool.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include +#include + +namespace mbgl { +namespace gl { + +class RendererBackend; + +class ResourceUploadThreadPool : public ThreadedScheduler { +public: + ResourceUploadThreadPool(RendererBackend& backend); +}; + +} // namespace gl +} // namespace mbgl diff --git a/src/mbgl/gl/texture2d.cpp b/src/mbgl/gl/texture2d.cpp index a1c9dd9572c..03c479297e3 100644 --- a/src/mbgl/gl/texture2d.cpp +++ b/src/mbgl/gl/texture2d.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace mbgl { namespace gl { @@ -35,8 +36,10 @@ Texture2D& Texture2D::setFormat(gfx::TexturePixelType pixelFormat_, gfx::Texture } Texture2D& Texture2D::setSize(mbgl::Size size_) noexcept { - size = size_; - storageDirty = true; + if (size != size_) { + size = size_; + storageDirty = true; + } return *this; } @@ -166,6 +169,8 @@ void Texture2D::unbind() noexcept { } void Texture2D::upload(const void* pixelData, const Size& size_) noexcept { + MLN_TRACE_FUNC(); + if (!textureResource || storageDirty || size_ == Size{0, 0} || size_ != size) { size = size_; @@ -176,6 +181,8 @@ void Texture2D::upload(const void* pixelData, const Size& size_) noexcept { } void Texture2D::uploadSubRegion(const void* pixelData, const Size& size_, uint16_t xOffset, uint16_t yOffset) noexcept { + MLN_TRACE_FUNC(); + using namespace platform; assert(textureResource); @@ -201,6 +208,8 @@ void Texture2D::uploadSubRegion(const void* pixelData, const Size& size_, uint16 } void Texture2D::upload() noexcept { + MLN_TRACE_FUNC(); + if (image && image->valid()) { setFormat(gfx::TexturePixelType::RGBA, gfx::TextureChannelDataType::UnsignedByte); upload(image->data.get(), image->size); diff --git a/src/mbgl/gl/upload_pass.cpp b/src/mbgl/gl/upload_pass.cpp index 3f6f7933f30..a70d6a02528 100644 --- a/src/mbgl/gl/upload_pass.cpp +++ b/src/mbgl/gl/upload_pass.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,41 @@ namespace gl { using namespace platform; +namespace { + +UniqueBuffer createUniqueBuffer( + gl::Context& mainContext, const void* data, std::size_t size, gfx::BufferUsageType usage, GLenum bufferGlTarget) { + MLN_TRACE_FUNC(); + + // mainContext is the main render thread context and is passed to UniqueBuffer deleter. + // only the main context deletes resources. Shared contexts only upload resources. + + // Note that we call glBindBuffer instead of setting commandEncoder.context.vertexBuffer + // This is because this function can be used in shared contexts, e.g. shared EGL contexts. + // States aren't tracked in shared contexts. When this function is called in the main + // render thread context then the caller must set commandEncoder.context.vertexBuffer + + BufferID id = 0; + MBGL_CHECK_ERROR(glGenBuffers(1, &id)); + MBGL_CHECK_ERROR(glBindBuffer(bufferGlTarget, id)); + MBGL_CHECK_ERROR(glBufferData(bufferGlTarget, size, data, Enum::to(usage))); + + // NOLINTNEXTLINE(performance-move-const-arg) + return UniqueBuffer{std::move(id), {mainContext}}; +} + +void updateUniqueBuffer(const UniqueBuffer& buffer, const void* data, std::size_t size, GLenum bufferGlTarget) { + MLN_TRACE_FUNC(); + + // Similar to createUniqueBuffer the caller must set commandEncoder.context.vertexBuffer when + // it is run in the main render thread context + + MBGL_CHECK_ERROR(glBindBuffer(bufferGlTarget, buffer.get())); + MBGL_CHECK_ERROR(glBufferSubData(bufferGlTarget, 0, size, data)); +} + +} // namespace + UploadPass::UploadPass(gl::CommandEncoder& commandEncoder_, const char* name) : commandEncoder(commandEncoder_), debugGroup(commandEncoder.createDebugGroup(name)) {} @@ -32,44 +68,115 @@ std::unique_ptr UploadPass::createVertexBufferResourc const std::size_t size, const gfx::BufferUsageType usage, bool /*persistent*/) { - BufferID id = 0; - MBGL_CHECK_ERROR(glGenBuffers(1, &id)); - commandEncoder.context.renderingStats().numBuffers++; - commandEncoder.context.renderingStats().memVertexBuffers += static_cast(size); - // NOLINTNEXTLINE(performance-move-const-arg) - UniqueBuffer result{std::move(id), {commandEncoder.context}}; - commandEncoder.context.vertexBuffer = result; - MBGL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, size, data, Enum::to(usage))); - return std::make_unique(std::move(result), static_cast(size)); + MLN_TRACE_FUNC(); + + constexpr GLenum bufferGlTarget = GL_ARRAY_BUFFER; + auto& ctx = commandEncoder.context; + auto& backend = ctx.getBackend(); + + ctx.renderingStats().numBuffers++; + ctx.renderingStats().memVertexBuffers += static_cast(size); + + if (backend.supportFreeThreadedUpload()) { + auto result = std::make_unique( + [&](int size_, gfx::BufferUsageType usage_, const void* data_) { + return createUniqueBuffer(ctx, data_, size_, usage_, bufferGlTarget); + }, + [&](const UniqueBuffer& buffer, int size_, const void* data_) { + updateUniqueBuffer(buffer, data_, size_, bufferGlTarget); + }, + static_cast(size)); + result->asyncAlloc(backend.getResourceUploadThreadPool(), static_cast(size), usage, data); + return result; + + } else { + UniqueBuffer result = createUniqueBuffer(ctx, data, size, usage, bufferGlTarget); + ctx.vertexBuffer = result; + return std::make_unique(std::move(result), static_cast(size)); + } } void UploadPass::updateVertexBufferResource(gfx::VertexBufferResource& resource, const void* data, std::size_t size) { - commandEncoder.context.vertexBuffer = static_cast(resource).getBuffer(); - MBGL_CHECK_ERROR(glBufferSubData(GL_ARRAY_BUFFER, 0, size, data)); + MLN_TRACE_FUNC(); + + constexpr GLenum bufferGlTarget = GL_ARRAY_BUFFER; + auto& ctx = commandEncoder.context; + auto& backend = ctx.getBackend(); + auto& glResource = static_cast(resource); + assert(static_cast(size) <= glResource.getByteSize()); + + if (backend.supportFreeThreadedUpload()) { + if (glResource.isAsyncPending()) { + // This happens if an allocation is allocated and then followed with an update + // This also happens is an uploaded resource in a previous frame has not been used + glResource.wait(); + } + glResource.asyncUpdate(backend.getResourceUploadThreadPool(), static_cast(size), data); + } else { + const UniqueBuffer& buffer = glResource.pickBuffer(); + ctx.vertexBuffer = buffer; + updateUniqueBuffer(buffer, data, size, bufferGlTarget); + } } std::unique_ptr UploadPass::createIndexBufferResource(const void* data, std::size_t size, const gfx::BufferUsageType usage, bool /*persistent*/) { - BufferID id = 0; - MBGL_CHECK_ERROR(glGenBuffers(1, &id)); - commandEncoder.context.renderingStats().numBuffers++; - commandEncoder.context.renderingStats().memIndexBuffers += static_cast(size); - // NOLINTNEXTLINE(performance-move-const-arg) - UniqueBuffer result{std::move(id), {commandEncoder.context}}; - commandEncoder.context.bindVertexArray = 0; - commandEncoder.context.globalVertexArrayState.indexBuffer = result; - MBGL_CHECK_ERROR(glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, data, Enum::to(usage))); - return std::make_unique(std::move(result), static_cast(size)); + MLN_TRACE_FUNC(); + + constexpr GLenum bufferGlTarget = GL_ELEMENT_ARRAY_BUFFER; + auto& ctx = commandEncoder.context; + auto& backend = ctx.getBackend(); + + ctx.renderingStats().numBuffers++; + ctx.renderingStats().memIndexBuffers += static_cast(size); + + if (backend.supportFreeThreadedUpload()) { + auto result = std::make_unique( + [&](int size_, gfx::BufferUsageType usage_, const void* data_) { + return createUniqueBuffer(ctx, data_, size_, usage_, bufferGlTarget); + }, + [&](const UniqueBuffer& buffer, int size_, const void* data_) { + updateUniqueBuffer(buffer, data_, size_, bufferGlTarget); + }, + static_cast(size)); + result->asyncAlloc(backend.getResourceUploadThreadPool(), static_cast(size), usage, data); + return result; + + } else { + ctx.bindVertexArray = 0; + ctx.globalVertexArrayState.indexBuffer = 0; + UniqueBuffer result = createUniqueBuffer(ctx, data, size, usage, bufferGlTarget); + ctx.globalVertexArrayState.indexBuffer = result; + return std::make_unique(std::move(result), static_cast(size)); + } } void UploadPass::updateIndexBufferResource(gfx::IndexBufferResource& resource, const void* data, std::size_t size) { - // Be sure to unbind any existing vertex array object before binding the - // index buffer so that we don't mess up another VAO - commandEncoder.context.bindVertexArray = 0; - commandEncoder.context.globalVertexArrayState.indexBuffer = static_cast(resource).buffer; - MBGL_CHECK_ERROR(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, size, data)); + MLN_TRACE_FUNC(); + + constexpr GLenum bufferGlTarget = GL_ELEMENT_ARRAY_BUFFER; + auto& ctx = commandEncoder.context; + auto& backend = ctx.getBackend(); + auto& glResource = static_cast(resource); + assert(static_cast(size) <= glResource.getByteSize()); + + if (backend.supportFreeThreadedUpload()) { + if (glResource.isAsyncPending()) { + // This happens if an allocation is allocated and then followed with an update + // This also happens is an uploaded resource in a previous frame has not been used + glResource.wait(); + } + glResource.asyncUpdate(backend.getResourceUploadThreadPool(), static_cast(size), data); + } else { + const UniqueBuffer& buffer = glResource.pickBuffer(); + // Be sure to unbind any existing vertex array object before binding the + // index buffer so that we don't mess up another VAO + ctx.bindVertexArray = 0; + ctx.globalVertexArrayState.indexBuffer = buffer; + updateUniqueBuffer(buffer, data, size, bufferGlTarget); + } } std::unique_ptr UploadPass::createTextureResource(const Size size, @@ -146,6 +253,8 @@ const std::unique_ptr noBuffer; } const gfx::UniqueVertexBufferResource& UploadPass::getBuffer(const gfx::VertexVectorBasePtr& vec, const gfx::BufferUsageType usage) { + MLN_TRACE_FUNC(); + if (vec) { const auto* rawBufPtr = vec->getRawData(); const auto rawBufSize = static_cast(vec->getRawCount() * vec->getRawSize()); @@ -196,6 +305,7 @@ gfx::AttributeBindingArray UploadPass::buildAttributeBindings( const std::chrono::duration lastUpdate, /*out*/ std::vector>& outBuffers) { MLN_TRACE_FUNC(); + AttributeBindingArray bindings; bindings.resize(defaults.allocatedSize()); diff --git a/src/mbgl/gl/upload_pass.hpp b/src/mbgl/gl/upload_pass.hpp index a1d1cfb2cfc..9f8b1f64dbf 100644 --- a/src/mbgl/gl/upload_pass.hpp +++ b/src/mbgl/gl/upload_pass.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace mbgl { diff --git a/src/mbgl/gl/value.cpp b/src/mbgl/gl/value.cpp index fe0840c723c..92fb6b3b157 100644 --- a/src/mbgl/gl/value.cpp +++ b/src/mbgl/gl/value.cpp @@ -610,7 +610,7 @@ void VertexAttribute::Set(const Type& binding, Context& context, AttributeLocati MLN_TRACE_FUNC_GL(); if (binding && binding->vertexBufferResource) { context.vertexBuffer = - reinterpret_cast(*binding->vertexBufferResource).getBuffer(); + static_cast(*binding->vertexBufferResource).waitAndGetBuffer(); MBGL_CHECK_ERROR(glEnableVertexAttribArray(location)); MBGL_CHECK_ERROR(glVertexAttribPointer( location, diff --git a/src/mbgl/gl/vertex_array.cpp b/src/mbgl/gl/vertex_array.cpp index 4705b8bb20b..7ce306682cd 100644 --- a/src/mbgl/gl/vertex_array.cpp +++ b/src/mbgl/gl/vertex_array.cpp @@ -1,13 +1,16 @@ #include #include #include +#include namespace mbgl { namespace gl { void VertexArray::bind(Context& context, const gfx::IndexBuffer& indexBuffer, const AttributeBindingArray& bindings) { + MLN_TRACE_FUNC(); + context.bindVertexArray = state->vertexArray; - state->indexBuffer = indexBuffer.getResource().buffer; + state->indexBuffer = indexBuffer.getResource().waitAndGetBuffer(); state->bindings.reserve(bindings.size()); diff --git a/src/mbgl/gl/vertex_buffer_resource.cpp b/src/mbgl/gl/vertex_buffer_resource.cpp index d1d71bb7432..011e449fd43 100644 --- a/src/mbgl/gl/vertex_buffer_resource.cpp +++ b/src/mbgl/gl/vertex_buffer_resource.cpp @@ -2,18 +2,30 @@ #include #include +#include + namespace mbgl { namespace gl { VertexBufferResource::VertexBufferResource(UniqueBuffer&& buffer_, int byteSize_) - : buffer(std::move(buffer_)), - byteSize(byteSize_) { - MLN_TRACE_ALLOC_VERTEX_BUFFER(buffer.get(), byteSize); + : BufferResource(std::move(buffer_), byteSize_) { + MLN_TRACE_ALLOC_VERTEX_BUFFER(buffer->get(), byteSize); +} + +VertexBufferResource::VertexBufferResource(AsyncAllocCallback alloc, AsyncUpdateCallback update, int byteSize_) + : BufferResource(std::move(alloc), std::move(update)) { + (void)byteSize_; // Unused if Tracy is not enabled + MLN_TRACE_ALLOC_VERTEX_BUFFER(buffer->get(), byteSize_); } VertexBufferResource::~VertexBufferResource() noexcept { - MLN_TRACE_FREE_VERTEX_BUFFER(buffer.get()); - auto& stats = buffer.get_deleter().context.renderingStats(); + MLN_TRACE_FUNC(); + // Must wait before destruction in case a resource is created but never used (wait never called) + wait(); + + auto& underlyingBuffer = *buffer; + MLN_TRACE_FREE_VERTEX_BUFFER(underlyingBuffer.get()); + auto& stats = underlyingBuffer.get_deleter().context.renderingStats(); stats.memVertexBuffers -= byteSize; assert(stats.memVertexBuffers >= 0); } diff --git a/src/mbgl/gl/vertex_buffer_resource.hpp b/src/mbgl/gl/vertex_buffer_resource.hpp index d7ec725d07d..33d4f1aee80 100644 --- a/src/mbgl/gl/vertex_buffer_resource.hpp +++ b/src/mbgl/gl/vertex_buffer_resource.hpp @@ -1,27 +1,29 @@ #pragma once #include -#include +#include #include namespace mbgl { namespace gl { -class VertexBufferResource : public gfx::VertexBufferResource { +class VertexBufferResource : public BufferResource, public gfx::VertexBufferResource { public: + using BufferResource::AsyncAllocCallback; + using BufferResource::AsyncUpdateCallback; + + // Create a vertex buffer resource that takes ownership of buffer_ VertexBufferResource(UniqueBuffer&& buffer_, int byteSize_); - ~VertexBufferResource() noexcept override; - int getByteSize() const { return byteSize; } + // Create a non-owning vertex buffer resource + VertexBufferResource(AsyncAllocCallback alloc, AsyncUpdateCallback update, int byteSize_); - const UniqueBuffer& getBuffer() const { return buffer; } + ~VertexBufferResource() noexcept override; std::chrono::duration getLastUpdated() const { return lastUpdated; } void setLastUpdated(std::chrono::duration time) { lastUpdated = time; } -protected: - UniqueBuffer buffer; - int byteSize; +private: std::chrono::duration lastUpdated = util::MonotonicTimer::now(); }; diff --git a/src/mbgl/mtl/layer_group.cpp b/src/mbgl/mtl/layer_group.cpp index c653334bd28..757ef16b045 100644 --- a/src/mbgl/mtl/layer_group.cpp +++ b/src/mbgl/mtl/layer_group.cpp @@ -17,7 +17,7 @@ namespace mtl { LayerGroup::LayerGroup(int32_t layerIndex_, std::size_t initialCapacity, std::string name_) : mbgl::LayerGroup(layerIndex_, initialCapacity, std::move(name_)) {} -void LayerGroup::upload(gfx::UploadPass& uploadPass) { +void LayerGroup::issueUpload(gfx::UploadPass& uploadPass) { if (!enabled) { return; } diff --git a/src/mbgl/mtl/tile_layer_group.cpp b/src/mbgl/mtl/tile_layer_group.cpp index 0a8202415b2..30219d2bb6a 100644 --- a/src/mbgl/mtl/tile_layer_group.cpp +++ b/src/mbgl/mtl/tile_layer_group.cpp @@ -19,7 +19,7 @@ namespace mtl { TileLayerGroup::TileLayerGroup(int32_t layerIndex_, std::size_t initialCapacity, std::string name_) : mbgl::TileLayerGroup(layerIndex_, initialCapacity, std::move(name_)) {} -void TileLayerGroup::upload(gfx::UploadPass& uploadPass) { +void TileLayerGroup::issueUpload(gfx::UploadPass& uploadPass) { if (!enabled || !getDrawableCount()) { return; } diff --git a/src/mbgl/renderer/render_target.cpp b/src/mbgl/renderer/render_target.cpp index 61bad83696d..17285dfba3a 100644 --- a/src/mbgl/renderer/render_target.cpp +++ b/src/mbgl/renderer/render_target.cpp @@ -60,8 +60,8 @@ const LayerGroupBasePtr& RenderTarget::getLayerGroup(const int32_t layerIndex) c return (hit == layerGroupsByLayerIndex.end()) ? no_group : hit->second; } -void RenderTarget::upload(gfx::UploadPass& uploadPass) { - visitLayerGroups(([&](LayerGroupBase& layerGroup) { layerGroup.upload(uploadPass); })); +void RenderTarget::issueUpload(gfx::UploadPass& uploadPass) { + visitLayerGroups(([&](LayerGroupBase& layerGroup) { layerGroup.issueUpload(uploadPass); })); } void RenderTarget::render(RenderOrchestrator& orchestrator, const RenderTree& renderTree, PaintParameters& parameters) { diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 2c4f23a935b..3688f8c2f4d 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -250,7 +250,6 @@ void Renderer::Impl::render(const RenderTree& renderTree, #if !defined(NDEBUG) const auto debugGroup = uploadPass->createDebugGroup("layerGroup-upload"); #endif - // Tweakers are run in the upload pass so they can set up uniforms. orchestrator.visitLayerGroups( [&](LayerGroupBase& layerGroup) { layerGroup.runTweakers(renderTree, parameters); }); @@ -261,13 +260,13 @@ void Renderer::Impl::render(const RenderTree& renderTree, orchestrator.updateDebugLayerGroups(renderTree, parameters); // Give the layers a chance to upload - orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); }); + orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.issueUpload(*uploadPass); }); // Give the render targets a chance to upload - orchestrator.visitRenderTargets([&](RenderTarget& renderTarget) { renderTarget.upload(*uploadPass); }); + orchestrator.visitRenderTargets([&](RenderTarget& renderTarget) { renderTarget.issueUpload(*uploadPass); }); // Upload the Debug layer group - orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); }); + orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.issueUpload(*uploadPass); }); } const Size atlasSize = parameters.patternAtlas.getPixelSize(); diff --git a/src/mbgl/util/thread_pool.cpp b/src/mbgl/util/thread_pool.cpp index bf2ad316df7..d396ff5de20 100644 --- a/src/mbgl/util/thread_pool.cpp +++ b/src/mbgl/util/thread_pool.cpp @@ -21,19 +21,27 @@ void ThreadedSchedulerBase::terminate() { cvAvailable.notify_all(); } -std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) { - return std::thread([this, index] { +std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index, + bool gatherTasks, + std::function callbacksGenerator, + const char* threadNamePrefix) { + return std::thread([this, index, gatherTasks, callbacksGen = std::move(callbacksGenerator), threadNamePrefix] { auto& settings = platform::Settings::getInstance(); auto value = settings.get(platform::EXPERIMENTAL_THREAD_PRIORITY_WORKER); if (auto* priority = value.getDouble()) { platform::setCurrentThreadPriority(*priority); } - platform::setCurrentThreadName("Worker " + util::toString(index + 1)); + platform::setCurrentThreadName(threadNamePrefix + util::toString(index + 1)); platform::attachThread(); owningThreadPool.set(this); + auto callbacks = callbacksGen(); + if (callbacks.onThreadBegin) { + callbacks.onThreadBegin(); + } + while (true) { std::unique_lock conditionLock(workerMutex); if (!terminated && taskCount == 0) { @@ -54,6 +62,9 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) { std::lock_guard lock(taggedQueueLock); for (const auto& [tag, queue] : taggedQueue) { pending.push_back(queue); + if (!gatherTasks) { + break; + } } } @@ -74,7 +85,16 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) { taskCount--; try { + if (callbacks.onTaskBegin) { + callbacks.onTaskBegin(); + } + tasklet(); + + if (callbacks.onTaskEnd) { + callbacks.onTaskEnd(); + } + tasklet = {}; // destroy the function and release its captures before unblocking `waitForEmpty` if (!--q->runningCount) { @@ -102,6 +122,10 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) { } } } + + if (callbacks.onThreadEnd) { + callbacks.onThreadEnd(); + } }); } @@ -166,7 +190,11 @@ void ThreadedSchedulerBase::waitForEmpty(const util::SimpleIdentity tag) { // After waiting for the queue to empty, go ahead and erase it from the map. { std::lock_guard lock(taggedQueueLock); - taggedQueue.erase(tagToFind); + auto it = taggedQueue.find(tagToFind); + assert(it != taggedQueue.end()); + if (it->second->queue.empty()) { + taggedQueue.erase(it); + } } } } diff --git a/src/mbgl/util/thread_pool.hpp b/src/mbgl/util/thread_pool.hpp index b1614666179..b996992059a 100644 --- a/src/mbgl/util/thread_pool.hpp +++ b/src/mbgl/util/thread_pool.hpp @@ -17,6 +17,25 @@ namespace mbgl { +// Optional callbacks to be called for a thread in a thread pool +// The thread running these callbacks assumes the calls are thread safe +// The thread running these callbacks doesn't handle exceptions thrown out of the callbacks +// Callbacks are optional and can be useful to bind and unbind OpenGL rendering contexts or +// to explicitly control thread local storage +struct ThreadCallbacks { + // Called when a thread is created + std::function onThreadBegin = nullptr; + + // Called before a thread is destroyed + std::function onThreadEnd = nullptr; + + // Called when the thread is about to start a task + std::function onTaskBegin = nullptr; + + // Called when the thread ends executing a task + std::function onTaskEnd = nullptr; +}; + class ThreadedSchedulerBase : public Scheduler { public: /// @brief Schedule a generic task not assigned to any particular owner. @@ -35,12 +54,25 @@ class ThreadedSchedulerBase : public Scheduler { ~ThreadedSchedulerBase() override; void terminate(); - std::thread makeSchedulerThread(size_t index); + + // Create a thread to runs tasks + // When gatherTasks is true (default), the thread gathers as many tasks as + // possible from taggedQueue then run them + // When gatherTasks is false, the thread picks one task, runs it then tries to pick another task + // gatherTasks==false is useful when the task producing thread is faster than the gathering thread + // in which case one gathering thread starves the other threads in the pool + // callbackGenerator is optional + std::thread makeSchedulerThread( + size_t index, + bool gatherTasks = true, + std::function callbacksGenerator = []() { return ThreadCallbacks{}; }, + const char* threadNamePrefix = "Worker"); /// @brief Wait until there's nothing pending or in process /// Must not be called from a task provided to this scheduler. /// @param tag Tag of the owner to identify the collection of tasks to // wait for. Not providing a tag waits on tasks owned by the scheduler. + /// @note The queue may not be empty if schedule() is called while waitForEmpty() is running void waitForEmpty(const util::SimpleIdentity = util::SimpleIdentity::Empty) override; /// Returns true if called from a thread managed by the scheduler @@ -74,10 +106,22 @@ class ThreadedSchedulerBase : public Scheduler { */ class ThreadedScheduler : public ThreadedSchedulerBase { public: - ThreadedScheduler(std::size_t n) + // Create a ThreadedScheduler with n threads to runs tasks + // When gatherTasks is true (default), the thread gathers as many tasks as + // possible from taggedQueue then run them + // When gatherTasks is false, the thread picks one task, runs it then tries to pick another task + // gatherTasks==false is useful when the task producing thread is faster than the gathering thread + // in which case one gathering thread starves the other threads in the pool + // callbacks are optional and can be useful to bind and unbind OpenGL rendering contexts or + // to explicitly control thread local storage + ThreadedScheduler( + std::size_t n, + bool gatherTasks = true, + std::function callbacksGenerator = []() { return ThreadCallbacks{}; }, + const char* threadNamePrefix = "Worker") : threads(n) { for (std::size_t i = 0u; i < threads.size(); ++i) { - threads[i] = makeSchedulerThread(i); + threads[i] = makeSchedulerThread(i, gatherTasks, callbacksGenerator, threadNamePrefix); } } diff --git a/src/mbgl/vulkan/layer_group.cpp b/src/mbgl/vulkan/layer_group.cpp index e5344c681bd..0390994396d 100644 --- a/src/mbgl/vulkan/layer_group.cpp +++ b/src/mbgl/vulkan/layer_group.cpp @@ -16,7 +16,7 @@ namespace vulkan { LayerGroup::LayerGroup(int32_t layerIndex_, std::size_t initialCapacity, std::string name_) : mbgl::LayerGroup(layerIndex_, initialCapacity, std::move(name_)) {} -void LayerGroup::upload(gfx::UploadPass& uploadPass) { +void LayerGroup::issueUpload(gfx::UploadPass& uploadPass) { if (!enabled) { return; } diff --git a/src/mbgl/vulkan/tile_layer_group.cpp b/src/mbgl/vulkan/tile_layer_group.cpp index 766c42cb475..787d68d9d9e 100644 --- a/src/mbgl/vulkan/tile_layer_group.cpp +++ b/src/mbgl/vulkan/tile_layer_group.cpp @@ -17,7 +17,7 @@ namespace vulkan { TileLayerGroup::TileLayerGroup(int32_t layerIndex_, std::size_t initialCapacity, std::string name_) : mbgl::TileLayerGroup(layerIndex_, initialCapacity, std::move(name_)) {} -void TileLayerGroup::upload(gfx::UploadPass& uploadPass) { +void TileLayerGroup::issueUpload(gfx::UploadPass& uploadPass) { if (!enabled || !getDrawableCount()) { return; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 814b31c1dc4..0abb5579ae5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -131,6 +131,7 @@ if(MLN_WITH_OPENGL) PRIVATE ${PROJECT_SOURCE_DIR}/test/api/custom_layer.test.cpp ${PROJECT_SOURCE_DIR}/test/api/custom_drawable_layer.test.cpp + ${PROJECT_SOURCE_DIR}/test/gl/async_resources.test.cpp ${PROJECT_SOURCE_DIR}/test/gl/bucket.test.cpp ${PROJECT_SOURCE_DIR}/test/gl/enum.test.cpp ${PROJECT_SOURCE_DIR}/test/gl/context.test.cpp diff --git a/test/gl/async_resources.test.cpp b/test/gl/async_resources.test.cpp new file mode 100644 index 00000000000..8feb772dbbf --- /dev/null +++ b/test/gl/async_resources.test.cpp @@ -0,0 +1,119 @@ +// GPU fences work fine with EGL, GLX and WGL but I noticed a crash when using QT +// This test is disabled for QT until the issue is resolved +#if MLN_RENDER_BACKEND_OPENGL && !defined(__QT__) + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mbgl; + +// Free threaded resources are currrently only supported with AndroidRendererBackend +// All other backends require GL commands to be issued from a single thread +// In order to test asynchronous resources with HeadlessBackend, this test mocks shared +// context by running asynchronous operations using a ResourceUploadThreadPool but ensures +// all operation are serialized, i.e. only one backend scope is active at a time + +// This mutex ensures one backend scope is active at any given time +// It is locked when a BackendScope is created and unlocked when it is destroyed +std::mutex& globalMutex() { + static std::mutex mutex; + return mutex; +} + +class BackendWithMockedSharedContexts : public gl::HeadlessBackend { +public: + BackendWithMockedSharedContexts() + : gl::HeadlessBackend({32, 32}) {} + + bool supportFreeThreadedUpload() const override { return true; } + + std::shared_ptr createUploadThreadContext() override; +}; + +class MockedUploadThreadContext : public gl::UploadThreadContext { +public: + MockedUploadThreadContext(BackendWithMockedSharedContexts& backend_) + : backend(backend_) {} + ~MockedUploadThreadContext() override = default; + void createContext() override {} + void destroyContext() override {} + void bindContext() override { + globalMutex().lock(); + backendScopePtr = std::make_unique(backend); + } + void unbindContext() override { + MBGL_CHECK_ERROR(platform::glFinish()); + backendScopePtr.reset(); + globalMutex().unlock(); + } + +private: + BackendWithMockedSharedContexts& backend; + std::unique_ptr backendScopePtr = nullptr; +}; + +std::shared_ptr BackendWithMockedSharedContexts::createUploadThreadContext() { + return std::make_shared(*this); +} + +gl::BufferID createBufferId(gl::Fence& fence) { + gl::BufferID id = 0; + MBGL_CHECK_ERROR(platform::glGenBuffers(1, &id)); + EXPECT_GT(id, 0); + fence.insert(); + return id; +} + +void updateMemStats(gl::Context& context, const gl::VertexBufferResource& resource) { + context.renderingStats().memVertexBuffers += resource.getByteSize(); + context.renderingStats().numBuffers++; +} + +void updateMemStats(gl::Context& context, const gl::IndexBufferResource& resource) { + context.renderingStats().memIndexBuffers += resource.getByteSize(); + context.renderingStats().numBuffers++; +} + +template +void testResource(BackendWithMockedSharedContexts& backend, gl::Context& context) { + constexpr int data = 0; + gl::Fence fence; + + auto buffer = std::make_unique( + [&](int, gfx::BufferUsageType, const void*) { return gl::UniqueBuffer{createBufferId(fence), {context}}; }, + [&](const gl::UniqueBuffer&, int, const void*) { + fence.gpuWait(); // Expected to never block in the GPU + }, + 0); + + buffer->asyncAlloc(backend.getResourceUploadThreadPool(), sizeof(data), gfx::BufferUsageType::StaticDraw, &data); + updateMemStats(context, *buffer); + buffer->wait(); + + buffer->asyncUpdate(backend.getResourceUploadThreadPool(), sizeof(data), &data); + buffer->wait(); + + { + std::lock_guard lock{globalMutex()}; + gfx::BackendScope scope{backend}; + EXPECT_TRUE(fence.isSignaled()); + fence.cpuWait(); // Expected to immediately return + } +} + +TEST(AsyncResources, AsyncResources) { + BackendWithMockedSharedContexts backend; + gl::Context context{backend}; + testResource(backend, context); + testResource(backend, context); +} + +#endif