From cb7ea2ccfbb5d81083313c21d6b85a9c85db67d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sat, 8 Aug 2020 01:18:18 +0200 Subject: GPUBackend: Add new GPUBackend object to manage GL object allocations This just set a global object responsible for allocating new objects in a thread safe way without needing any GPUContext bound to this thread. This also introduce the GLContext which will contain all the GL related functions for the current context. # Conflicts: # source/blender/gpu/intern/gpu_context.cc --- source/blender/gpu/intern/gpu_context.cc | 178 +++++++--------------- source/blender/gpu/intern/gpu_context_private.h | 49 +++--- source/blender/gpu/intern/gpu_init_exit.c | 2 - source/blender/gpu/opengl/gl_backend.hh | 23 ++- source/blender/gpu/opengl/gl_context.cc | 191 +++++++++++++++++++++++- source/blender/gpu/opengl/gl_context.hh | 65 ++++++-- 6 files changed, 341 insertions(+), 167 deletions(-) (limited to 'source/blender/gpu') diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index 9707c32cede..d9f435ce1a9 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -52,58 +52,34 @@ #include #include -static std::vector orphaned_buffer_ids; -static std::vector orphaned_texture_ids; - -static std::mutex orphans_mutex; -static std::mutex main_context_mutex; +using namespace blender::gpu; static thread_local GPUContext *active_ctx = NULL; -static void orphans_add(GPUContext *ctx, std::vector *orphan_list, GLuint id) -{ - std::mutex *mutex = (ctx) ? &ctx->orphans_mutex : &orphans_mutex; +/* -------------------------------------------------------------------- */ +/** \name GPUContext methods + * \{ */ - mutex->lock(); - orphan_list->emplace_back(id); - mutex->unlock(); +GPUContext::GPUContext() +{ + thread_ = pthread_self(); + is_active_ = false; + matrix_state = GPU_matrix_state_create(); } -static void orphans_clear(GPUContext *ctx) +GPUContext::~GPUContext() { - /* need at least an active context */ - BLI_assert(ctx); - - /* context has been activated by another thread! */ - BLI_assert(pthread_equal(pthread_self(), ctx->thread)); + GPU_matrix_state_discard(matrix_state); +} - ctx->orphans_mutex.lock(); - if (!ctx->orphaned_vertarray_ids.empty()) { - uint orphan_len = (uint)ctx->orphaned_vertarray_ids.size(); - glDeleteVertexArrays(orphan_len, ctx->orphaned_vertarray_ids.data()); - ctx->orphaned_vertarray_ids.clear(); - } - if (!ctx->orphaned_framebuffer_ids.empty()) { - uint orphan_len = (uint)ctx->orphaned_framebuffer_ids.size(); - glDeleteFramebuffers(orphan_len, ctx->orphaned_framebuffer_ids.data()); - ctx->orphaned_framebuffer_ids.clear(); - } +bool GPUContext::is_active_on_thread(void) +{ + return (this == active_ctx) && pthread_equal(pthread_self(), thread_); +} - ctx->orphans_mutex.unlock(); +/** \} */ - orphans_mutex.lock(); - if (!orphaned_buffer_ids.empty()) { - uint orphan_len = (uint)orphaned_buffer_ids.size(); - glDeleteBuffers(orphan_len, orphaned_buffer_ids.data()); - orphaned_buffer_ids.clear(); - } - if (!orphaned_texture_ids.empty()) { - uint orphan_len = (uint)orphaned_texture_ids.size(); - glDeleteTextures(orphan_len, orphaned_texture_ids.data()); - orphaned_texture_ids.clear(); - } - orphans_mutex.unlock(); -} +/* -------------------------------------------------------------------- */ GPUContext *GPU_context_create(void *ghost_window) { @@ -113,15 +89,7 @@ GPUContext *GPU_context_create(void *ghost_window) } GPUContext *ctx = gpu_backend_get()->context_alloc(ghost_window); - glGenVertexArrays(1, &ctx->default_vao); - if (ghost_window != NULL) { - ctx->default_framebuffer = GHOST_GetDefaultOpenGLFramebuffer((GHOST_WindowHandle)ghost_window); - } - else { - ctx->default_framebuffer = 0; - } - ctx->matrix_state = GPU_matrix_state_create(); GPU_context_active_set(ctx); return ctx; } @@ -129,21 +97,6 @@ GPUContext *GPU_context_create(void *ghost_window) /* to be called after GPU_context_active_set(ctx_to_destroy) */ void GPU_context_discard(GPUContext *ctx) { - /* Make sure no other thread has locked it. */ - BLI_assert(ctx == active_ctx); - BLI_assert(pthread_equal(pthread_self(), ctx->thread)); - BLI_assert(ctx->orphaned_vertarray_ids.empty()); -#ifdef DEBUG - /* For now don't allow GPUFrameBuffers to be reuse in another ctx. */ - BLI_assert(ctx->framebuffers.empty()); -#endif - /* delete remaining vaos */ - while (!ctx->batches.empty()) { - /* this removes the array entry */ - GPU_batch_vao_cache_clear(*ctx->batches.begin()); - } - GPU_matrix_state_discard(ctx->matrix_state); - glDeleteVertexArrays(1, &ctx->default_vao); delete ctx; active_ctx = NULL; } @@ -151,22 +104,15 @@ void GPU_context_discard(GPUContext *ctx) /* ctx can be NULL */ void GPU_context_active_set(GPUContext *ctx) { -#if TRUST_NO_ONE if (active_ctx) { - active_ctx->thread_is_used = false; + active_ctx->deactivate(); } - /* Make sure no other context is already bound to this thread. */ - if (ctx) { - /* Make sure no other thread has locked it. */ - assert(ctx->thread_is_used == false); - ctx->thread = pthread_self(); - ctx->thread_is_used = true; - } -#endif + + active_ctx = ctx; + if (ctx) { - orphans_clear(ctx); + ctx->activate(); } - active_ctx = ctx; } GPUContext *GPU_context_active_get(void) @@ -177,23 +123,18 @@ GPUContext *GPU_context_active_get(void) GLuint GPU_vao_default(void) { BLI_assert(active_ctx); /* need at least an active context */ - BLI_assert(pthread_equal( - pthread_self(), active_ctx->thread)); /* context has been activated by another thread! */ - return active_ctx->default_vao; + return static_cast(active_ctx)->default_vao_; } GLuint GPU_framebuffer_default(void) { BLI_assert(active_ctx); /* need at least an active context */ - BLI_assert(pthread_equal( - pthread_self(), active_ctx->thread)); /* context has been activated by another thread! */ - return active_ctx->default_framebuffer; + return static_cast(active_ctx)->default_framebuffer_; } GLuint GPU_vao_alloc(void) { GLuint new_vao_id = 0; - orphans_clear(active_ctx); glGenVertexArrays(1, &new_vao_id); return new_vao_id; } @@ -201,7 +142,6 @@ GLuint GPU_vao_alloc(void) GLuint GPU_fbo_alloc(void) { GLuint new_fbo_id = 0; - orphans_clear(active_ctx); glGenFramebuffers(1, &new_fbo_id); return new_fbo_id; } @@ -209,7 +149,6 @@ GLuint GPU_fbo_alloc(void) GLuint GPU_buf_alloc(void) { GLuint new_buffer_id = 0; - orphans_clear(active_ctx); glGenBuffers(1, &new_buffer_id); return new_buffer_id; } @@ -217,51 +156,32 @@ GLuint GPU_buf_alloc(void) GLuint GPU_tex_alloc(void) { GLuint new_texture_id = 0; - orphans_clear(active_ctx); glGenTextures(1, &new_texture_id); return new_texture_id; } void GPU_vao_free(GLuint vao_id, GPUContext *ctx) { - BLI_assert(ctx); - if (ctx == active_ctx) { - glDeleteVertexArrays(1, &vao_id); - } - else { - orphans_add(ctx, &ctx->orphaned_vertarray_ids, vao_id); - } + static_cast(ctx)->vao_free(vao_id); } void GPU_fbo_free(GLuint fbo_id, GPUContext *ctx) { - BLI_assert(ctx); - if (ctx == active_ctx) { - glDeleteFramebuffers(1, &fbo_id); - } - else { - orphans_add(ctx, &ctx->orphaned_framebuffer_ids, fbo_id); - } + static_cast(ctx)->fbo_free(fbo_id); } void GPU_buf_free(GLuint buf_id) { - if (active_ctx) { - glDeleteBuffers(1, &buf_id); - } - else { - orphans_add(NULL, &orphaned_buffer_ids, buf_id); - } + /* TODO avoid using backend */ + GPUBackend *backend = gpu_backend_get(); + static_cast(backend)->buf_free(buf_id); } void GPU_tex_free(GLuint tex_id) { - if (active_ctx) { - glDeleteTextures(1, &tex_id); - } - else { - orphans_add(NULL, &orphaned_texture_ids, tex_id); - } + /* TODO avoid using backend */ + GPUBackend *backend = gpu_backend_get(); + static_cast(backend)->tex_free(tex_id); } /* GPUBatch & GPUFrameBuffer contains respectively VAO & FBO indices @@ -271,26 +191,20 @@ void GPU_tex_free(GLuint tex_id) void gpu_context_add_batch(GPUContext *ctx, GPUBatch *batch) { BLI_assert(ctx); - ctx->orphans_mutex.lock(); - ctx->batches.emplace(batch); - ctx->orphans_mutex.unlock(); + static_cast(ctx)->batch_register(batch); } void gpu_context_remove_batch(GPUContext *ctx, GPUBatch *batch) { BLI_assert(ctx); - ctx->orphans_mutex.lock(); - ctx->batches.erase(batch); - ctx->orphans_mutex.unlock(); + static_cast(ctx)->batch_unregister(batch); } void gpu_context_add_framebuffer(GPUContext *ctx, GPUFrameBuffer *fb) { #ifdef DEBUG BLI_assert(ctx); - ctx->orphans_mutex.lock(); - ctx->framebuffers.emplace(fb); - ctx->orphans_mutex.unlock(); + static_cast(ctx)->framebuffer_register(fb); #else UNUSED_VARS(ctx, fb); #endif @@ -300,9 +214,7 @@ void gpu_context_remove_framebuffer(GPUContext *ctx, GPUFrameBuffer *fb) { #ifdef DEBUG BLI_assert(ctx); - ctx->orphans_mutex.lock(); - ctx->framebuffers.erase(fb); - ctx->orphans_mutex.unlock(); + static_cast(ctx)->framebuffer_unregister(fb); #else UNUSED_VARS(ctx, fb); #endif @@ -324,6 +236,14 @@ struct GPUMatrixState *gpu_context_active_matrix_state_get() return active_ctx->matrix_state; } +/* -------------------------------------------------------------------- */ +/** \name Main context global mutex + * + * Used to avoid crash on some old drivers. + * \{ */ + +static std::mutex main_context_mutex; + void GPU_context_main_lock(void) { main_context_mutex.lock(); @@ -334,6 +254,8 @@ void GPU_context_main_unlock(void) main_context_mutex.unlock(); } +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Backend selection * \{ */ @@ -358,7 +280,11 @@ void GPU_backend_init(eGPUBackendType backend_type) void GPU_backend_exit(void) { - delete g_backend; + if (g_backend) { + /* TODO assert no resource left. Currently UI textures are still not freed in their context + * correctly. */ + delete g_backend; + } } GPUBackend *gpu_backend_get(void) diff --git a/source/blender/gpu/intern/gpu_context_private.h b/source/blender/gpu/intern/gpu_context_private.h index 374a05bc25f..37bf110665f 100644 --- a/source/blender/gpu/intern/gpu_context_private.h +++ b/source/blender/gpu/intern/gpu_context_private.h @@ -25,6 +25,8 @@ #pragma once +#include "MEM_guardedalloc.h" + #include "GPU_context.h" /* TODO cleanup this ifdef */ @@ -37,34 +39,29 @@ # include struct GPUFrameBuffer; +struct GPUMatrixState; struct GPUContext { - GLuint default_vao; - GLuint default_framebuffer; - GPUFrameBuffer *current_fbo; - std::unordered_set batches; /* Batches that have VAOs from this context */ -# ifdef DEBUG - std::unordered_set - framebuffers; /* Framebuffers that have FBO from this context */ -# endif - struct GPUMatrixState *matrix_state; - std::vector orphaned_vertarray_ids; - std::vector orphaned_framebuffer_ids; - std::mutex orphans_mutex; /* todo: try spinlock instead */ -# if TRUST_NO_ONE - pthread_t thread; /* Thread on which this context is active. */ - bool thread_is_used; -# endif - - GPUContext() - { -# if TRUST_NO_ONE - thread_is_used = false; -# endif - current_fbo = 0; - }; - - virtual ~GPUContext(){}; + public: + /** State managment */ + GPUFrameBuffer *current_fbo = NULL; + GPUMatrixState *matrix_state = NULL; + + protected: + /** Thread on which this context is active. */ + pthread_t thread_; + bool is_active_; + + public: + GPUContext(); + virtual ~GPUContext(); + + virtual void activate(void) = 0; + virtual void deactivate(void) = 0; + + bool is_active_on_thread(void); + + MEM_CXX_CLASS_ALLOC_FUNCS("GPUContext") }; #endif diff --git a/source/blender/gpu/intern/gpu_init_exit.c b/source/blender/gpu/intern/gpu_init_exit.c index 462a1d395c2..ba0da95eb9d 100644 --- a/source/blender/gpu/intern/gpu_init_exit.c +++ b/source/blender/gpu/intern/gpu_init_exit.c @@ -93,8 +93,6 @@ void GPU_exit(void) gpu_extensions_exit(); gpu_platform_exit(); /* must come last */ - GPU_backend_exit(); - initialized = false; } diff --git a/source/blender/gpu/opengl/gl_backend.hh b/source/blender/gpu/opengl/gl_backend.hh index 25400a55394..f7c01b2f184 100644 --- a/source/blender/gpu/opengl/gl_backend.hh +++ b/source/blender/gpu/opengl/gl_backend.hh @@ -25,12 +25,33 @@ #include "gpu_backend.hh" +#include "BLI_vector.hh" + #include "gl_context.hh" +namespace blender { +namespace gpu { + class GLBackend : public GPUBackend { + private: + GLSharedOrphanLists shared_orphan_list_; + public: GPUContext *context_alloc(void *ghost_window) { - return new GLContext(ghost_window); + return new GLContext(ghost_window, shared_orphan_list_); }; + + /* TODO remove */ + void buf_free(GLuint buf_id); + void tex_free(GLuint tex_id); + void orphans_add(Vector &orphan_list, std::mutex &list_mutex, unsigned int id) + { + list_mutex.lock(); + orphan_list.append(id); + list_mutex.unlock(); + } }; + +} // namespace gpu +} // namespace blender diff --git a/source/blender/gpu/opengl/gl_context.cc b/source/blender/gpu/opengl/gl_context.cc index 9fe283131fd..696603ab070 100644 --- a/source/blender/gpu/opengl/gl_context.cc +++ b/source/blender/gpu/opengl/gl_context.cc @@ -30,24 +30,209 @@ #include "gpu_context_private.h" +#include "gl_backend.hh" /* TODO remove */ #include "gl_context.hh" -// TODO(fclem) this requires too much refactor for now. -// using namespace blender::gpu; +using namespace blender::gpu; /* -------------------------------------------------------------------- */ /** \name Constructor / Destructor * \{ */ -GLContext::GLContext(void *ghost_window) : GPUContext() +GLContext::GLContext(void *ghost_window, GLSharedOrphanLists &shared_orphan_list) + : shared_orphan_list_(shared_orphan_list) { default_framebuffer_ = ghost_window ? GHOST_GetDefaultOpenGLFramebuffer((GHOST_WindowHandle)ghost_window) : 0; + + glGenVertexArrays(1, &default_vao_); + + float data[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + glGenBuffers(1, &default_attr_vbo_); + glBindBuffer(GL_ARRAY_BUFFER, default_attr_vbo_); + glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); } GLContext::~GLContext() { + BLI_assert(orphaned_framebuffers_.is_empty()); + BLI_assert(orphaned_vertarrays_.is_empty()); + /* For now don't allow GPUFrameBuffers to be reuse in another context. */ + BLI_assert(framebuffers_.is_empty()); + /* Delete vaos so the batch can be reused in another context. */ + for (GPUBatch *batch : batches_) { + GPU_batch_vao_cache_clear(batch); + } + glDeleteVertexArrays(1, &default_vao_); + glDeleteBuffers(1, &default_attr_vbo_); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Activate / Deactivate context + * \{ */ + +void GLContext::activate(void) +{ + /* Make sure no other context is already bound to this thread. */ + BLI_assert(is_active_ == false); + + is_active_ = true; + thread_ = pthread_self(); + + /* Clear accumulated orphans. */ + orphans_clear(); +} + +void GLContext::deactivate(void) +{ + is_active_ = false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Safe object deletion + * + * GPU objects can be freed when the context is not bound. + * In this case we delay the deletion until the context is bound again. + * \{ */ + +void GLSharedOrphanLists::orphans_clear(void) +{ + /* Check if any context is active on this thread! */ + BLI_assert(GPU_context_active_get()); + + lists_mutex.lock(); + if (!buffers.is_empty()) { + glDeleteBuffers((uint)buffers.size(), buffers.data()); + buffers.clear(); + } + if (!textures.is_empty()) { + glDeleteTextures((uint)textures.size(), textures.data()); + textures.clear(); + } + lists_mutex.unlock(); +}; + +void GLContext::orphans_clear(void) +{ + /* Check if context has been activated by another thread! */ + BLI_assert(this->is_active_on_thread()); + + lists_mutex_.lock(); + if (!orphaned_vertarrays_.is_empty()) { + glDeleteVertexArrays((uint)orphaned_vertarrays_.size(), orphaned_vertarrays_.data()); + orphaned_vertarrays_.clear(); + } + if (!orphaned_framebuffers_.is_empty()) { + glDeleteFramebuffers((uint)orphaned_framebuffers_.size(), orphaned_framebuffers_.data()); + orphaned_framebuffers_.clear(); + } + lists_mutex_.unlock(); + + shared_orphan_list_.orphans_clear(); +}; + +void GLContext::orphans_add(Vector &orphan_list, std::mutex &list_mutex, GLuint id) +{ + list_mutex.lock(); + orphan_list.append(id); + list_mutex.unlock(); +} + +void GLContext::vao_free(GLuint vao_id) +{ + if (this == GPU_context_active_get()) { + glDeleteVertexArrays(1, &vao_id); + } + else { + orphans_add(orphaned_vertarrays_, lists_mutex_, vao_id); + } +} + +void GLContext::fbo_free(GLuint fbo_id) +{ + if (this == GPU_context_active_get()) { + glDeleteFramebuffers(1, &fbo_id); + } + else { + orphans_add(orphaned_framebuffers_, lists_mutex_, fbo_id); + } +} + +void GLBackend::buf_free(GLuint buf_id) +{ + /* Any context can free. */ + if (GPU_context_active_get()) { + glDeleteBuffers(1, &buf_id); + } + else { + orphans_add(shared_orphan_list_.buffers, shared_orphan_list_.lists_mutex, buf_id); + } +} + +void GLBackend::tex_free(GLuint tex_id) +{ + /* Any context can free. */ + if (GPU_context_active_get()) { + glDeleteTextures(1, &tex_id); + } + else { + orphans_add(shared_orphan_list_.textures, shared_orphan_list_.lists_mutex, tex_id); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Linked object deletion + * + * These objects contain data that are stored per context. We + * need to do some cleanup if they are used accross context or if context + * is discarded. + * \{ */ + +void GLContext::batch_register(struct GPUBatch *batch) +{ + lists_mutex_.lock(); + batches_.add(batch); + lists_mutex_.unlock(); +} + +void GLContext::batch_unregister(struct GPUBatch *batch) +{ + /* vao_cache_clear() can acquire lists_mutex_ so avoid deadlock. */ + // reinterpret_cast(batch)->vao_cache_clear(); + + lists_mutex_.lock(); + batches_.remove(batch); + lists_mutex_.unlock(); +} + +void GLContext::framebuffer_register(struct GPUFrameBuffer *fb) +{ +#ifdef DEBUG + lists_mutex_.lock(); + framebuffers_.add(fb); + lists_mutex_.unlock(); +#else + UNUSED_VARS(fb); +#endif +} + +void GLContext::framebuffer_unregister(struct GPUFrameBuffer *fb) +{ +#ifdef DEBUG + lists_mutex_.lock(); + framebuffers_.remove(fb); + lists_mutex_.unlock(); +#else + UNUSED_VARS(fb); +#endif } /** \} */ diff --git a/source/blender/gpu/opengl/gl_context.hh b/source/blender/gpu/opengl/gl_context.hh index 5daf793910c..5da288a265f 100644 --- a/source/blender/gpu/opengl/gl_context.hh +++ b/source/blender/gpu/opengl/gl_context.hh @@ -25,6 +25,9 @@ #include "gpu_context_private.h" +#include "BLI_vector.hh" +#include "BLI_set.hh" + #include "glew-mx.h" #include @@ -32,19 +35,63 @@ #include #include -// TODO(fclem) this requires too much refactor for now. -// namespace blender { -// namespace gpu { +namespace blender { +namespace gpu { -class GLContext : public GPUContext { +class GLSharedOrphanLists { public: - GLContext(void *ghost_window); - ~GLContext(); + /** Mutex for the bellow structures. */ + std::mutex lists_mutex; + /** Buffers and textures are shared across context. Any context can free them. */ + Vector textures; + Vector buffers; + + public: + void orphans_clear(void); +}; - private: +class GLContext : public GPUContext { + /* TODO(fclem) these needs to become private. */ + public: + /** Default VAO for procedural draw calls. */ + GLuint default_vao_; /** Default framebuffer object for some GL implementation. */ GLuint default_framebuffer_; + /** VBO for missing vertex attrib binding. Avoid undefined behavior on some implementation. */ + GLuint default_attr_vbo_; + /** + * GPUBatch & GPUFramebuffer have references to the context they are from, in the case the + * context is destroyed, we need to remove any reference to it. + */ + Set batches_; + Set framebuffers_; + /** Mutex for the bellow structures. */ + std::mutex lists_mutex_; + /** VertexArrays and framebuffers are not shared across context. */ + Vector orphaned_vertarrays_; + Vector orphaned_framebuffers_; + /** GLBackend onws this data. */ + GLSharedOrphanLists &shared_orphan_list_; + + public: + GLContext(void *ghost_window, GLSharedOrphanLists &shared_orphan_list); + ~GLContext(); + + void activate(void) override; + void deactivate(void) override; + + /* TODO(fclem) these needs to become private. */ + public: + void orphans_add(Vector &orphan_list, std::mutex &list_mutex, GLuint id); + void orphans_clear(void); + + void vao_free(GLuint vao_id); + void fbo_free(GLuint fbo_id); + void batch_register(struct GPUBatch *batch); + void batch_unregister(struct GPUBatch *batch); + void framebuffer_register(struct GPUFrameBuffer *fb); + void framebuffer_unregister(struct GPUFrameBuffer *fb); }; -// } // namespace gpu -// } // namespace blender +} // namespace gpu +} // namespace blender -- cgit v1.2.3