diff --git a/CMakeLists.txt b/CMakeLists.txt index 38a496d40d..c71c725159 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1083,6 +1083,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 @@ -1111,6 +1113,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 faad8fbaf3..1fadb485bd 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -870,6 +870,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", @@ -898,6 +900,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 f088003dca..f5a9c0f418 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&); void updateVertexAttributes(gfx::VertexAttributeArrayPtr, std::size_t vertexCount, diff --git a/include/mbgl/gl/layer_group_gl.hpp b/include/mbgl/gl/layer_group_gl.hpp index 0250a85458..9cf7c4f232 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 145c1f78f2..473c726326 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,10 @@ class RendererBackend : public gfx::RendererBackend { void initShaders(gfx::ShaderRegistry&, const ProgramParameters& programParameters) override; #endif + virtual bool supportFreeThreadedUpload() { return false; } + virtual std::shared_ptr createUploadThreadContext() { return nullptr; } + gl::ResourceUploadThreadPool& getResourceUploadThreadPool(); + protected: std::unique_ptr createContext() override; @@ -52,12 +67,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 90f742942c..69a3c005ea 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 89b51e804e..83ff2dc25c 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 27b6cdcded..7e8f92826d 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 e368034af5..6e220db355 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 9ce3abf550..0223259218 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 ba867b81e5..20a8221ecb 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 079d3af85c..f525213237 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 ebf5df7888..64feca0fa9 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,24 @@ #include #include #include - -#include +#include +#include +#include #include +#include +#include namespace mbgl { namespace android { +namespace { + +std::mutex& getEglMutex() { + static std::mutex eglMutex; + return eglMutex; +} + +} // namespace class AndroidGLRenderableResource final : public mbgl::gl::RenderableResource { public: @@ -33,11 +44,14 @@ class AndroidGLRenderableResource final : public mbgl::gl::RenderableResource { AndroidGLRendererBackend& backend; }; -AndroidGLRendererBackend::AndroidGLRendererBackend() +AndroidGLRendererBackend::AndroidGLRendererBackend(bool multiThreadedGpuResourceUpload_) : gl::RendererBackend(gfx::ContextMode::Unique), - mbgl::gfx::Renderable({64, 64}, std::make_unique(*this)) {} + mbgl::gfx::Renderable({64, 64}, std::make_unique(*this)), + multiThreadedGpuResourceUpload(multiThreadedGpuResourceUpload_) {} -AndroidGLRendererBackend::~AndroidGLRendererBackend() = default; +AndroidGLRendererBackend::~AndroidGLRendererBackend() { + destroyResourceUploadThreadPool(); +} gl::ProcAddress AndroidGLRendererBackend::getExtensionFunctionPointer(const char* name) { assert(gfx::BackendScope::exists()); @@ -69,6 +83,220 @@ void AndroidGLRendererBackend::markContextLost() { } } +bool AndroidGLRendererBackend::supportFreeThreadedUpload() { + if (multiThreadedGpuResourceUpload) { + initFreeThreadedUpload(); + return eglClientVersion >= 3; + } else { + return false; + } +} + +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"; + Log::Error(Event::OpenGL, err); + throw std::runtime_error(err); + } + + eglDsply = eglGetCurrentDisplay(); + if (eglDsply == EGL_NO_DISPLAY) { + constexpr const char* err = "eglGetCurrentDisplay returned EGL_NO_DISPLAY"; + Log::Error(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"; + Log::Error(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"; + Log::Error(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()); + Log::Error(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()); + Log::Error(Event::OpenGL, err); + throw std::runtime_error(err); + } + if (config_count != 1) { + auto err = "eglChooseConfig returned multiple configs: " + std::to_string(config_count); + Log::Error(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()); + Log::Error(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()); + Log::Error(Event::OpenGL, err); + throw std::runtime_error(err); + } + Log::Debug(Event::OpenGL, "Running MapLibre Native with OpenGL ES version " + std::to_string(eglClientVersion)); + if (eglClientVersion < 3) { + // Fence sync objects are not supported with OpenGL ES 2.0. They may be supported as + // extensions but we simply require core OpenGL ES 3.0 to be available to enable shared contexts + Log::Error( + Event::OpenGL, + "Multithreaded resource uploads is not supported with OpenGL ES " + std::to_string(eglClientVersion)); + multiThreadedGpuResourceUpload = false; + } +} + +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) { + Log::Error(Event::OpenGL, "AndroidUploadThreadContext::destroyContext() must be explicitly called"); + } else { + Log::Error(Event::OpenGL, "Unexpected context bound to an Upload thread"); + } +} + +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); + +#ifdef MLN_EGL_DEBUG + int attribs[] = {EGL_CONTEXT_CLIENT_VERSION, clientVersion, EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE, EGL_NONE}; +#else + int attribs[] = {EGL_CONTEXT_CLIENT_VERSION, clientVersion, EGL_NONE}; +#endif + sharedContext = eglCreateContext(display, config, mainContext, attribs); + if (sharedContext == EGL_NO_CONTEXT) { + auto err = "eglCreateContext returned EGL_NO_CONTEXT. Error code " + std::to_string(eglGetError()); + Log::Error(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()); + Log::Error(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()); + Log::Error(Event::OpenGL, err); + throw std::runtime_error(err); + } + MLN_TRACE_GL_CONTEXT(); +} + +void AndroidUploadThreadContext::destroyContext() { + MLN_TRACE_FUNC(); + + const std::lock_guard lock(getEglMutex()); + + // On Mali drivers EGL shared contexts functions fail when the main context is destroyed before the + // shared contexts. i.e. FinalizerDaemon is run after the main context is destroyed. + + if (eglWaitClient() == EGL_FALSE) { + auto err = "eglWaitClient failed. Error code " + std::to_string(eglGetError()); + Log::Warning(Event::OpenGL, err); + } + + auto ctx = eglGetCurrentContext(); + if (ctx == EGL_NO_CONTEXT) { + constexpr const char* err = + "AndroidUploadThreadContext::destroyContext() expects a persistently bound EGL shared context"; + Log::Warning(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"; + Log::Warning(Event::OpenGL, err); + } + + 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()); + Log::Warning(Event::OpenGL, err); + } + if (eglDestroyContext(display, sharedContext) == EGL_FALSE) { + auto err = "eglDestroyContext failed. Error code " + std::to_string(eglGetError()); + Log::Warning(Event::OpenGL, err); + } + if (eglDestroySurface(display, surface) == EGL_FALSE) { + auto err = "eglDestroySurface failed. Error code " + std::to_string(eglGetError()); + Log::Warning(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 @@ -76,8 +304,9 @@ namespace mbgl { namespace gfx { template <> -std::unique_ptr Backend::Create(ANativeWindow*) { - return std::make_unique(); +std::unique_ptr Backend::Create( + ANativeWindow*, bool multiThreadedGpuResourceUpload) { + return std::make_unique(multiThreadedGpuResourceUpload); } } // namespace gfx 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 c27755cc7c..a98306e39a 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,8 @@ #include #include "android_renderer_backend.hpp" +#include + namespace mbgl { namespace android { @@ -11,7 +13,7 @@ class AndroidGLRendererBackend : public AndroidRendererBackend, public gl::RendererBackend, public mbgl::gfx::Renderable { public: - AndroidGLRendererBackend(); + AndroidGLRendererBackend(bool multiThreadedGpuResourceUpload); ~AndroidGLRendererBackend() override; mbgl::gfx::RendererBackend& getImpl() override { return *this; } @@ -24,6 +26,9 @@ class AndroidGLRendererBackend : public AndroidRendererBackend, void resizeFramebuffer(int width, int height) override; PremultipliedImage readFramebuffer() override; + bool supportFreeThreadedUpload() override; + std::shared_ptr createUploadThreadContext() override; + // mbgl::gfx::RendererBackend implementation public: mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; } @@ -40,6 +45,36 @@ class AndroidGLRendererBackend : public AndroidRendererBackend, protected: mbgl::gl::ProcAddress getExtensionFunctionPointer(const char*) override; void updateAssumedState() override; + +private: + void initFreeThreadedUpload(); + +private: + int eglClientVersion = 0; + EGLContext eglMainCtx = EGL_NO_CONTEXT; + EGLDisplay eglDsply = EGL_NO_DISPLAY; + EGLSurface eglSurf = EGL_NO_SURFACE; + EGLConfig eglConfig; + bool multiThreadedGpuResourceUpload = false; +}; + +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/android/MapLibreAndroid/src/cpp/android_renderer_backend.hpp b/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.hpp index 3f538c100f..66c496b46d 100644 --- a/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.hpp @@ -17,8 +17,9 @@ class AndroidRendererBackend { AndroidRendererBackend& operator=(const AndroidRendererBackend&) = delete; virtual ~AndroidRendererBackend() = default; - static std::unique_ptr Create(ANativeWindow* window) { - return mbgl::gfx::Backend::Create(window); + static std::unique_ptr Create(ANativeWindow* window, bool multiThreadedGpuResourceUpload) { + return mbgl::gfx::Backend::Create(window, + multiThreadedGpuResourceUpload); } virtual mbgl::gfx::RendererBackend& getImpl() = 0; diff --git a/platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.cpp b/platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.cpp index b9c1ebd6d7..dab3eada29 100644 --- a/platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.cpp @@ -1,6 +1,7 @@ #include "android_vulkan_renderer_backend.hpp" #include +#include #include #include @@ -71,7 +72,11 @@ namespace gfx { template <> std::unique_ptr Backend::Create( - ANativeWindow* window) { + ANativeWindow* window, bool multiThreadedGpuResourceUpload) { + if (multiThreadedGpuResourceUpload) { + mbgl::Log::Error(mbgl::Event::Render, + "Multi-threaded GPU resource upload flag has no effect in a Vulkan backend"); + } return std::make_unique(window); } diff --git a/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp b/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp index fc235a67f4..0d6d110df2 100644 --- a/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp @@ -19,11 +19,13 @@ namespace android { MapRenderer::MapRenderer(jni::JNIEnv& _env, const jni::Object& obj, jni::jfloat pixelRatio_, - const jni::String& localIdeographFontFamily_) + const jni::String& localIdeographFontFamily_, + jni::jboolean multiThreadedGpuResourceUpload_) : javaPeer(_env, obj), pixelRatio(pixelRatio_), localIdeographFontFamily(localIdeographFontFamily_ ? jni::Make(_env, localIdeographFontFamily_) : std::optional{}), + multiThreadedGpuResourceUpload(multiThreadedGpuResourceUpload_), threadPool(Scheduler::GetBackground(), {}), mailboxData(this) {} @@ -228,7 +230,7 @@ void MapRenderer::onSurfaceCreated(JNIEnv& env, const jni::Object(backend->getImpl(), pixelRatio, localIdeographFontFamily); rendererRef = std::make_unique>(*renderer, mailboxData.getMailbox()); @@ -287,7 +289,7 @@ void MapRenderer::registerNative(jni::JNIEnv& env) { env, javaClass, "nativePtr", - jni::MakePeer&, jni::jfloat, const jni::String&>, + jni::MakePeer&, jni::jfloat, const jni::String&, jni::jboolean>, "nativeInitialize", "finalize", METHOD(&MapRenderer::render, "nativeRender"), diff --git a/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp b/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp index 9fc962c0d3..d339338c91 100644 --- a/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp @@ -49,7 +49,8 @@ class MapRenderer : public Scheduler { MapRenderer(jni::JNIEnv& _env, const jni::Object&, jni::jfloat pixelRatio, - const jni::String& localIdeographFontFamily); + const jni::String& localIdeographFontFamily, + jni::jboolean multiThreadedGpuResourceUpload); ~MapRenderer() override; @@ -128,6 +129,7 @@ class MapRenderer : public Scheduler { float pixelRatio; std::optional localIdeographFontFamily; + bool multiThreadedGpuResourceUpload = false; TaggedScheduler threadPool; const MailboxData mailboxData; diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMapOptions.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMapOptions.java index 4f35ceecdb..fdc17491fb 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMapOptions.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMapOptions.java @@ -94,6 +94,8 @@ public class MapLibreMapOptions implements Parcelable { private boolean crossSourceCollisions = true; + private boolean multiThreadedGpuResourceUploadEnabled = true; + /** * Creates a new MapLibreMapOptions object. * @@ -151,6 +153,8 @@ private MapLibreMapOptions(Parcel in) { pixelRatio = in.readFloat(); foregroundLoadColor = in.readInt(); crossSourceCollisions = in.readByte() != 0; + + multiThreadedGpuResourceUploadEnabled = in.readByte() != 0; } /** @@ -730,6 +734,18 @@ public MapLibreMapOptions crossSourceCollisions(boolean crossSourceCollisions) { return this; } + /** + * Specifies if multi-threaded Gpu Resource Uploads are enabled for a map view. + * + * @param enabled True and multi-threaded Gpu Resource Uploads will be enabled + * @return This + */ + @NonNull + public MapLibreMapOptions multiThreadedGpuResourceUploadEnabled(boolean enabled) { + multiThreadedGpuResourceUploadEnabled = enabled; + return this; + } + /** * Enable local ideograph font family, defaults to true. * @@ -1144,6 +1160,15 @@ public float getPixelRatio() { return pixelRatio; } + /** + * Get the current multi-threaded Gpu Resource Upload state for a map view. + * + * @return True multi-threaded Gpu Resource Uploads are enabled + */ + public boolean getMultiThreadedGpuResourceUploadEnabled() { + return multiThreadedGpuResourceUploadEnabled; + } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public MapLibreMapOptions createFromParcel(@NonNull Parcel in) { return new MapLibreMapOptions(in); @@ -1205,6 +1230,8 @@ public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeFloat(pixelRatio); dest.writeInt(foregroundLoadColor); dest.writeByte((byte) (crossSourceCollisions ? 1 : 0)); + + dest.writeByte((byte) (multiThreadedGpuResourceUploadEnabled ? 1 : 0)); } @Override @@ -1325,7 +1352,11 @@ public boolean equals(@Nullable Object o) { return false; } - return false; + if (multiThreadedGpuResourceUploadEnabled != options.multiThreadedGpuResourceUploadEnabled) { + return false; + } + + return true; } @Override @@ -1372,6 +1403,7 @@ public int hashCode() { result = 31 * result + Arrays.hashCode(localIdeographFontFamilies); result = 31 * result + (int) pixelRatio; result = 31 * result + (crossSourceCollisions ? 1 : 0); + result = 31 * result + (multiThreadedGpuResourceUploadEnabled ? 1 : 0); return result; } } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java index 24ca1af317..b179ac2431 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java @@ -56,26 +56,36 @@ public static MapRenderer create(MapLibreMapOptions options, @NonNull Context co MapRenderer renderer = null; String localFontFamily = options.getLocalIdeographFontFamily(); + boolean multiThreadedGpuResourceUpload = options.getMultiThreadedGpuResourceUploadEnabled(); if (options.getTextureMode()) { TextureView textureView = new TextureView(context); boolean translucentSurface = options.getTranslucentTextureSurface(); - renderer = MapRendererFactory.newTextureViewMapRenderer(context, textureView, localFontFamily, - translucentSurface, initCallback); + renderer = MapRendererFactory.newTextureViewMapRenderer(context, + textureView, + localFontFamily, + multiThreadedGpuResourceUpload, + translucentSurface, + initCallback); } else { boolean renderSurfaceOnTop = options.getRenderSurfaceOnTop(); - renderer = MapRendererFactory.newSurfaceViewMapRenderer(context, localFontFamily, - renderSurfaceOnTop, initCallback); + renderer = MapRendererFactory.newSurfaceViewMapRenderer(context, + localFontFamily, + multiThreadedGpuResourceUpload, + renderSurfaceOnTop, + initCallback); } return renderer; } - public MapRenderer(@NonNull Context context, String localIdeographFontFamily) { + public MapRenderer(@NonNull Context context, + String localIdeographFontFamily, + boolean multiThreadedGpuResourceUpload) { float pixelRatio = context.getResources().getDisplayMetrics().density; // Initialize native peer - nativeInitialize(this, pixelRatio, localIdeographFontFamily); + nativeInitialize(this, pixelRatio, localIdeographFontFamily, multiThreadedGpuResourceUpload); } public abstract View getView(); @@ -164,7 +174,8 @@ void queueEvent(MapRendererRunnable runnable) { private native void nativeInitialize(MapRenderer self, float pixelRatio, - String localIdeographFontFamily); + String localIdeographFontFamily, + boolean multiThreadedGpuResourceUpload); @CallSuper @Override diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java index 99445e8e14..131a2977f3 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java @@ -21,8 +21,9 @@ public class SurfaceViewMapRenderer extends MapRenderer { public SurfaceViewMapRenderer(Context context, MapLibreSurfaceView surfaceView, - String localIdeographFontFamily) { - super(context, localIdeographFontFamily); + String localIdeographFontFamily, + boolean multiThreadedGpuResourceUpload) { + super(context, localIdeographFontFamily, multiThreadedGpuResourceUpload); this.surfaceView = surfaceView; surfaceView.setDetachedListener(new MapLibreSurfaceView.OnSurfaceViewDetachedListener() { diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java index 64623ea3ac..7bd7a6d38f 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java @@ -31,8 +31,9 @@ public class TextureViewMapRenderer extends MapRenderer { public TextureViewMapRenderer(@NonNull Context context, @NonNull TextureView textureView, String localIdeographFontFamily, + boolean multiThreadedGpuResourceUpload, boolean translucentSurface) { - super(context, localIdeographFontFamily); + super(context, localIdeographFontFamily, multiThreadedGpuResourceUpload); this.textureView = textureView; this.translucentSurface = translucentSurface; diff --git a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/MapRendererFactory.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/MapRendererFactory.java index 64320ecd3f..39c487e820 100644 --- a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/MapRendererFactory.java +++ b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/MapRendererFactory.java @@ -15,12 +15,15 @@ @Keep public class MapRendererFactory { - public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context, TextureView textureView, - String localFontFamily, boolean translucentSurface, + public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context, + TextureView textureView, + String localFontFamily, + boolean multiThreadedGpuResourceUpload, + boolean translucentSurface, Runnable initCallback) { TextureViewMapRenderer mapRenderer = new TextureViewMapRenderer(context, textureView, - localFontFamily, translucentSurface) { + localFontFamily, multiThreadedGpuResourceUpload, translucentSurface) { @Override protected void onSurfaceCreated(Surface surface) { initCallback.run(); @@ -32,13 +35,16 @@ protected void onSurfaceCreated(Surface surface) { return mapRenderer; } - public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@NonNull Context context, String localFontFamily, - boolean renderSurfaceOnTop, Runnable initCallback) { + public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@NonNull Context context, + String localFontFamily, + boolean multiThreadedGpuResourceUpload, + boolean renderSurfaceOnTop, + Runnable initCallback) { MapLibreGLSurfaceView surfaceView = new MapLibreGLSurfaceView(context); surfaceView.setZOrderMediaOverlay(renderSurfaceOnTop); - return new GLSurfaceViewMapRenderer(context, surfaceView, localFontFamily) { + return new GLSurfaceViewMapRenderer(context, surfaceView, localFontFamily, multiThreadedGpuResourceUpload) { @Override public void onSurfaceCreated(Surface surface) { initCallback.run(); diff --git a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLContextFactory.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLContextFactory.java index bf5c4f815e..6385bad09f 100644 --- a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLContextFactory.java +++ b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLContextFactory.java @@ -11,13 +11,24 @@ import javax.microedition.khronos.egl.EGLDisplay; public class EGLContextFactory implements GLSurfaceView.EGLContextFactory { + private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; public EGLContext createContext(EGL10 egl, @Nullable EGLDisplay display, @Nullable EGLConfig config) { if (display == null || config == null) { return EGL10.EGL_NO_CONTEXT; } - int[] attrib_list = {0x3098, 2, EGL10.EGL_NONE}; - return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrib_list); + // Try and get a GLES 3 context + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE}; + EGLContext context = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrib_list); + if (context == EGL10.EGL_NO_CONTEXT) { + Log.e("DefaultContextFactory", "Failed to create an OpenGL ES 3 context. Retrying with OpenGL ES 2..."); + attrib_list[1] = 2; + context = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrib_list); + } + if (context == EGL10.EGL_NO_CONTEXT) { + Log.e("DefaultContextFactory", "Failed to create an OpenGL ES 3 or OpenGL ES 2 context."); + } + return context; } public void destroyContext(EGL10 egl, EGLDisplay display, diff --git a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java index 9d05530a91..3f6026a57f 100644 --- a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java +++ b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java @@ -12,8 +12,9 @@ public class GLSurfaceViewMapRenderer extends SurfaceViewMapRenderer { public GLSurfaceViewMapRenderer(Context context, @NonNull MapLibreGLSurfaceView surfaceView, - String localIdeographFontFamily) { - super(context, surfaceView, localIdeographFontFamily); + String localIdeographFontFamily, + boolean multiThreadedGpuResourceUpload) { + super(context, surfaceView, localIdeographFontFamily, multiThreadedGpuResourceUpload); surfaceView.setEGLContextFactory(new EGLContextFactory()); surfaceView.setEGLWindowSurfaceFactory(new EGLWindowSurfaceFactory()); diff --git a/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/MapRendererFactory.java b/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/MapRendererFactory.java index 2ac7878319..f708aee111 100644 --- a/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/MapRendererFactory.java +++ b/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/MapRendererFactory.java @@ -15,12 +15,15 @@ @Keep public class MapRendererFactory { - public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context, TextureView textureView, - String localFontFamily, boolean translucentSurface, + public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context, + TextureView textureView, + String localFontFamily, + boolean multiThreadedGpuResourceUpload, + boolean translucentSurface, Runnable initCallback) { TextureViewMapRenderer mapRenderer = new TextureViewMapRenderer(context, textureView, - localFontFamily, translucentSurface) { + localFontFamily, multiThreadedGpuResourceUpload, translucentSurface) { @Override protected void onSurfaceCreated(Surface surface) { initCallback.run(); @@ -32,13 +35,16 @@ protected void onSurfaceCreated(Surface surface) { return mapRenderer; } - public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@NonNull Context context, String localFontFamily, - boolean renderSurfaceOnTop, Runnable initCallback) { + public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@NonNull Context context, + String localFontFamily, + boolean multiThreadedGpuResourceUpload, + boolean renderSurfaceOnTop, + Runnable initCallback) { MapLibreVulkanSurfaceView surfaceView = new MapLibreVulkanSurfaceView(context); surfaceView.setZOrderMediaOverlay(renderSurfaceOnTop); - return new VulkanSurfaceViewMapRenderer(context, surfaceView, localFontFamily) { + return new VulkanSurfaceViewMapRenderer(context, surfaceView, localFontFamily, multiThreadedGpuResourceUpload) { @Override public void onSurfaceCreated(Surface surface) { initCallback.run(); diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt index 0de98bb754..15e74510d9 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt @@ -435,7 +435,7 @@ class NativeMapViewTest : AppCenter() { assertEquals(transitionOptions, nativeMapView.transitionOptions) } - class DummyRenderer(context: Context) : MapRenderer(context, null) { + class DummyRenderer(context: Context) : MapRenderer(context, null, false) { private var renderingRefreshMode: RenderingRefreshMode = RenderingRefreshMode.WHEN_DIRTY diff --git a/platform/default/include/mbgl/gl/headless_backend.hpp b/platform/default/include/mbgl/gl/headless_backend.hpp index 85ca7c5cb7..26965ba55c 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 2ff1ef573d..8b7ec4613b 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 0000000000..61a1574d9a --- /dev/null +++ b/src/mbgl/gl/buffer_resource.cpp @@ -0,0 +1,150 @@ +#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); + gpuFence.gpuWait(); + gpuFence.reset(); + 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; + } + gpuFence.insert(); + + 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 0000000000..0553bdf2ea --- /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 03216eaa3d..a8d7c5f654 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 ae3b50cf84..94da219b13 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 70ad47a91c..84dcc81ce2 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 @@ -30,6 +31,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()) { @@ -161,15 +164,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; } @@ -180,16 +175,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 && (!impl->indexes->getBuffer() || impl->indexes->getDirty())) { @@ -229,33 +217,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(); }; @@ -282,6 +243,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 787c528961..108dc73c0f 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; @@ -49,20 +72,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 350939e6cf..c8f06c7006 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 9edb413caa..8b703a385f 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 c5b558ecde..1f6036146e 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 5cbb202392..468bf1c9ef 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 9e4ada2ec6..320be028f8 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 fa8badc2d6..b13536d7d4 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,15 @@ 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) { + 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 0000000000..0693f2399e --- /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 0000000000..a5df437800 --- /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 a1c9dd9572..03c479297e 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 8243675ff7..ea7b6c7f5c 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::optional> 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 4c6a38c7d8..0e3f9f3f06 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 fe0840c723..92fb6b3b15 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 4705b8bb20..7ce306682c 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 d1d71bb743..011e449fd4 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 d7ec725d07..33d4f1aee8 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 c653334bd2..757ef16b04 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 0a8202415b..30219d2bb6 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 61bad83696..17285dfba3 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 e8a098764e..c3cd87fc86 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 bf2ad316df..d396ff5de2 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 b161466617..b996992059 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 f86d556e03..5ff59dab80 100644 --- a/src/mbgl/vulkan/layer_group.cpp +++ b/src/mbgl/vulkan/layer_group.cpp @@ -18,7 +18,7 @@ LayerGroup::LayerGroup(int32_t layerIndex_, std::size_t initialCapacity, std::st : mbgl::LayerGroup(layerIndex_, initialCapacity, std::move(name_)), uniformBuffers(DescriptorSetType::Layer, shaders::layerUBOStartId, shaders::maxUBOCountPerLayer) {} -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 ac56147305..9a116832bc 100644 --- a/src/mbgl/vulkan/tile_layer_group.cpp +++ b/src/mbgl/vulkan/tile_layer_group.cpp @@ -19,7 +19,7 @@ TileLayerGroup::TileLayerGroup(int32_t layerIndex_, std::size_t initialCapacity, : mbgl::TileLayerGroup(layerIndex_, initialCapacity, std::move(name_)), uniformBuffers(DescriptorSetType::Layer, shaders::layerUBOStartId, shaders::maxUBOCountPerLayer) {} -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 5c70fc9517..dec7d5f824 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -133,6 +133,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 0000000000..4db37af274 --- /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() 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