Files
test/source/blender/draw/intern/draw_gpu_context.cc
Miguel Pozo 88f812bf9a Fix #141253: Bring back the global Draw lock
Alternative solution to #141392 / #141564.

As a recap, the DST global lock (which prevented running drawing code
from multiple threads concurrently) was removed for 4.5 (#134690).
One unforeseen issue is that Images (and their GPUTextures) are shared
across dependency graphs (and therefore multiple threads), meaning we
are running into data race issues with them.

@fclem did #141392 and I continued it #141564. However, this is only a
partial solution, parts of the GPUTexture API and the whole BKE_image
API are still unsafe.
Trying to solve all the possible underlying issues seems unrealistic for
4.5 given the time frame and that the extension of the code affected by
this issue is quite large.

So this PR just brings the 4.4 locking behavior instead, which, while
risky on its own, seems much safer to me than the alternative.

This effectively undoes the improvements from #134690 by disabling
concurrent rendering, but instead of reverting all the code, it just
ensures we hold the lock in the same places we did in 4.4.
This means there's some redundant code that is not technically needed
anymore, like the `submission_mutex`, but it's probably best to make as
few modifications as possible, given how close we are to release and
that this is only intended as a temporary measure.

Pull Request: https://projects.blender.org/blender/blender/pulls/141618
2025-07-09 15:11:29 +02:00

447 lines
12 KiB
C++

/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BKE_global.hh"
#include "GPU_capabilities.hh"
#include "GPU_context.hh"
#include "GPU_state.hh"
#include "RE_engine.h"
#include "RE_pipeline.h"
#include "DRW_engine.hh"
#include "DRW_render.hh"
#include "WM_api.hh"
#include "wm_window.hh"
/* -------------------------------------------------------------------- */
/** \name Submission critical section
*
* The usage of GPUShader objects is currently not thread safe. Since they are shared resources
* between render engine instances, we cannot allow pass submissions in a concurrent manner.
* \{ */
static TicketMutex *draw_mutex = nullptr;
static TicketMutex *submission_mutex = nullptr;
void DRW_mutexes_init()
{
draw_mutex = BLI_ticket_mutex_alloc();
submission_mutex = BLI_ticket_mutex_alloc();
}
void DRW_mutexes_exit()
{
BLI_ticket_mutex_free(draw_mutex);
BLI_ticket_mutex_free(submission_mutex);
}
void DRW_lock_start()
{
bool locked = BLI_ticket_mutex_lock_check_recursive(draw_mutex);
BLI_assert(locked);
UNUSED_VARS_NDEBUG(locked);
}
void DRW_lock_end()
{
BLI_ticket_mutex_unlock(draw_mutex);
}
void DRW_submission_start()
{
bool locked = BLI_ticket_mutex_lock_check_recursive(submission_mutex);
BLI_assert(locked);
UNUSED_VARS_NDEBUG(locked);
GPU_render_begin();
}
void DRW_submission_end()
{
GPU_render_end();
BLI_ticket_mutex_unlock(submission_mutex);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name ContextShared
*
* \{ */
/* Context that can be shared across threads. Usage is guarded by a ticket mutex.
* Should eventually be moved to GPU module after we get rid of the WM calls. */
class ContextShared {
/* Should be private but needs to be public for XR workaround.*/
public:
TicketMutex *mutex_ = nullptr;
/** Unique ghost context used by Viewports. */
void *system_gpu_context_ = nullptr;
/** GPUContext associated to the system_gpu_context. */
GPUContext *blender_gpu_context_ = nullptr;
/* NOTE: This changes the active context. */
ContextShared()
{
mutex_ = BLI_ticket_mutex_alloc();
system_gpu_context_ = WM_system_gpu_context_create();
WM_system_gpu_context_activate(system_gpu_context_);
blender_gpu_context_ = GPU_context_create(nullptr, system_gpu_context_);
}
~ContextShared()
{
WM_system_gpu_context_activate(system_gpu_context_);
GPU_context_active_set(blender_gpu_context_);
GPU_context_discard(blender_gpu_context_);
WM_system_gpu_context_dispose(system_gpu_context_);
BLI_ticket_mutex_free(mutex_);
}
void enable()
{
DRW_lock_start();
/* IMPORTANT: We don't support immediate mode in render mode!
* This shall remain in effect until immediate mode supports
* multiple threads. */
BLI_ticket_mutex_lock(mutex_);
GPU_render_begin();
WM_system_gpu_context_activate(system_gpu_context_);
GPU_context_active_set(blender_gpu_context_);
GPU_context_begin_frame(blender_gpu_context_);
}
/* Restore window drawable after disabling if restore is true. */
void disable(bool restore = false)
{
GPU_context_end_frame(blender_gpu_context_);
if (BLI_thread_is_main() && restore) {
wm_window_reset_drawable();
}
else {
WM_system_gpu_context_release(system_gpu_context_);
GPU_context_active_set(nullptr);
}
/* Render boundaries are opened and closed here as this may be
* called outside of an existing render loop. */
GPU_render_end();
BLI_ticket_mutex_unlock(mutex_);
DRW_lock_end();
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPU & System Context
*
* A global GPUContext is used for rendering every viewports (even on different windows).
* This is because some resources cannot be shared between contexts (GPUFramebuffers, GPUBatch).
* \{ */
/** Unique context used by Viewports. */
static ContextShared *viewport_context = nullptr;
/** Unique context used by Preview jobs. */
static ContextShared *preview_context = nullptr;
void DRW_gpu_context_create()
{
BLI_assert(viewport_context == nullptr); /* Ensure it's called once */
DRW_mutexes_init();
viewport_context = MEM_new<ContextShared>(__func__);
preview_context = MEM_new<ContextShared>(__func__);
/* Some part of the code assumes no context is left bound. */
GPU_context_active_set(nullptr);
WM_system_gpu_context_release(preview_context->system_gpu_context_);
/* Activate the window's context if any. */
wm_window_reset_drawable();
}
void DRW_gpu_context_destroy()
{
BLI_assert(BLI_thread_is_main());
if (viewport_context == nullptr) {
return;
}
DRW_mutexes_exit();
MEM_SAFE_DELETE(viewport_context);
MEM_SAFE_DELETE(preview_context);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Draw GPU Context
* \{ */
void DRW_gpu_context_enable_ex(bool /*restore*/)
{
if (viewport_context == nullptr) {
return;
}
viewport_context->enable();
}
void DRW_gpu_context_disable_ex(bool restore)
{
if (viewport_context == nullptr) {
return;
}
viewport_context->disable(restore);
}
static void drw_gpu_preview_context_enable()
{
if (preview_context == nullptr) {
return;
}
preview_context->enable();
}
static void drw_gpu_preview_context_disable()
{
if (preview_context == nullptr) {
return;
}
preview_context->disable();
}
void DRW_gpu_context_enable()
{
/* TODO: should be replace by a more elegant alternative. */
if (G.background && viewport_context == nullptr) {
WM_init_gpu();
}
DRW_gpu_context_enable_ex(true);
}
bool DRW_gpu_context_try_enable()
{
if (viewport_context == nullptr) {
return false;
}
DRW_gpu_context_enable_ex(true);
return true;
}
void DRW_gpu_context_disable()
{
DRW_gpu_context_disable_ex(true);
}
void DRW_system_gpu_render_context_enable(void *re_system_gpu_context)
{
/* If thread is main you should use DRW_gpu_context_enable(). */
BLI_assert(!BLI_thread_is_main());
DRW_lock_start();
WM_system_gpu_context_activate(re_system_gpu_context);
}
void DRW_system_gpu_render_context_disable(void *re_system_gpu_context)
{
WM_system_gpu_context_release(re_system_gpu_context);
DRW_lock_end();
}
void DRW_blender_gpu_render_context_enable(void *re_gpu_context)
{
/* If thread is main you should use DRW_gpu_context_enable(). */
BLI_assert(!BLI_thread_is_main());
GPU_context_active_set(static_cast<GPUContext *>(re_gpu_context));
}
void DRW_blender_gpu_render_context_disable(void * /*re_gpu_context*/)
{
GPU_flush();
GPU_context_active_set(nullptr);
}
void DRW_render_context_enable(Render *render)
{
if (G.background && viewport_context == nullptr) {
WM_init_gpu();
}
GPU_render_begin();
if (GPU_use_main_context_workaround()) {
GPU_context_main_lock();
DRW_gpu_context_enable();
return;
}
void *re_viewport_system_gpu_context = RE_system_gpu_context_get(render);
/* Changing Context */
if (re_viewport_system_gpu_context != nullptr) {
DRW_system_gpu_render_context_enable(re_viewport_system_gpu_context);
/* We need to query gpu context after a gl context has been bound. */
void *re_viewport_context = RE_blender_gpu_context_ensure(render);
DRW_blender_gpu_render_context_enable(re_viewport_context);
}
else {
drw_gpu_preview_context_enable();
}
}
void DRW_render_context_disable(Render *render)
{
if (GPU_use_main_context_workaround()) {
DRW_gpu_context_disable();
GPU_render_end();
GPU_context_main_unlock();
return;
}
void *re_viewport_system_gpu_context = RE_system_gpu_context_get(render);
if (re_viewport_system_gpu_context != nullptr) {
void *re_viewport_context = RE_blender_gpu_context_ensure(render);
/* GPU rendering may occur during context disable. */
DRW_blender_gpu_render_context_disable(re_viewport_context);
GPU_render_end();
DRW_system_gpu_render_context_disable(re_viewport_system_gpu_context);
}
else {
/* Usually the case for a preview job. The `Render` is created inside the render thread which
* is too late to create a GPU context. */
drw_gpu_preview_context_disable();
GPU_render_end();
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name XR
* \{ */
#ifdef WITH_XR_OPENXR
void *DRW_system_gpu_context_get()
{
/* XXX: There should really be no such getter, but for VR we currently can't easily avoid it.
* OpenXR needs some low level info for the GPU context that will be used for submitting the
* final frame-buffer. VR could in theory create its own context, but that would mean we have to
* switch to it just to submit the final frame, which has notable performance impact.
*
* We could "inject" a context through DRW_system_gpu_render_context_enable(), but that would
* have to work from the main thread, which is tricky to get working too. The preferable solution
* would be using a separate thread for VR drawing where a single context can stay active. */
return viewport_context->system_gpu_context_;
}
void *DRW_xr_blender_gpu_context_get()
{
/* XXX: See comment on #DRW_system_gpu_context_get(). */
return viewport_context->blender_gpu_context_;
}
void DRW_xr_drawing_begin()
{
/* XXX: See comment on #DRW_system_gpu_context_get(). */
DRW_lock_start();
BLI_ticket_mutex_lock(viewport_context->mutex_);
}
void DRW_xr_drawing_end()
{
/* XXX: See comment on #DRW_system_gpu_context_get(). */
BLI_ticket_mutex_unlock(viewport_context->mutex_);
DRW_lock_end();
}
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Draw manager context release/activation
*
* These functions are used in cases when an GPU context creation is needed during the draw.
* This happens, for example, when an external engine needs to create its own GPU context from
* the engine initialization.
*
* Example of context creation:
*
* const bool drw_state = DRW_gpu_context_release();
* viewport_system_gpu_context = WM_system_gpu_context_create();
* DRW_gpu_context_activate(drw_state);
*
* Example of context destruction:
*
* const bool drw_state = DRW_gpu_context_release();
* WM_system_gpu_context_activate(viewport_system_gpu_context);
* WM_system_gpu_context_dispose(viewport_system_gpu_context);
* DRW_gpu_context_activate(drw_state);
*
*
* NOTE: Will only perform context modification when on main thread. This way these functions can
* be used in an engine without check on whether it is a draw manager which manages GPU context
* on the current thread. The downside of this is that if the engine performs GPU creation from
* a non-main thread, that thread is supposed to not have GPU context ever bound by Blender.
*
* \{ */
bool DRW_gpu_context_release()
{
if (!BLI_thread_is_main()) {
return false;
}
if (GPU_context_active_get() != viewport_context->blender_gpu_context_) {
/* Context release is requested from the outside of the draw manager main draw loop, indicate
* this to the `DRW_gpu_context_activate()` so that it restores drawable of the window.
*/
return false;
}
GPU_context_active_set(nullptr);
WM_system_gpu_context_release(viewport_context->system_gpu_context_);
return true;
}
void DRW_gpu_context_activate(bool drw_state)
{
if (!BLI_thread_is_main()) {
return;
}
if (drw_state) {
WM_system_gpu_context_activate(viewport_context->system_gpu_context_);
GPU_context_active_set(viewport_context->blender_gpu_context_);
}
else {
wm_window_reset_drawable();
}
}
/** \} */