Files
test2/source/blender/draw/intern/draw_gpu_context.cc
Bart van der Braak a80b4ce22b Fix #145566: Crash in GPU_shader_builtin_warm_up when unbound GL context
glCreateProgram was called without a valid OpenGL context after
DRW_gpu_context_create unbound it. Rebind context before GPU_init
to prevent crash.

Fixes #145566

Pull Request: https://projects.blender.org/blender/blender/pulls/145606

Co-Authored-By: Miguel Pozo <pragma37@gmail.com>
2025-09-03 12:39:29 +02:00

452 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 gpu::Shader 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_);
}
bool is_enabled()
{
return blender_gpu_context_ == GPU_context_active_get();
}
/* 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__);
viewport_context->enable();
}
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;
}
bool DRW_gpu_context_is_enabled()
{
return viewport_context && viewport_context->is_enabled();
}
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();
}
}
/** \} */