From d870d7092b67dd482f0bca32bb48b37bce58f75d Mon Sep 17 00:00:00 2001 From: Michael Parkin-White Date: Mon, 13 Mar 2023 11:24:26 +0000 Subject: [PATCH 1/2] GPU: Add GPU frame capture support. Adds two modes of GPU frame capture support for enhanced debugging. GPU frame capture begin/end allow instantaneous frame capture of all GPU commands within the capture boundary. GPU frame capture scopes allow several user-defined capture regions which can wrap key parts of code. These scopes are exposed to connected GPU tools allowing the user to manually trigger a capture of a known scope at the desired time. This is currently integrated with the Metal backend for support with Xcode. Related to #105591 --- source/blender/gpu/GPU_debug.h | 50 ++++++++++ .../blender/gpu/intern/gpu_context_private.hh | 8 ++ source/blender/gpu/intern/gpu_debug.cc | 93 +++++++++++++++++++ source/blender/gpu/metal/mtl_context.hh | 5 + source/blender/gpu/metal/mtl_debug.mm | 58 ++++++++++++ source/blender/gpu/opengl/gl_context.hh | 5 + source/blender/gpu/opengl/gl_debug.cc | 23 +++++ source/blender/gpu/vulkan/vk_context.cc | 23 +++++ source/blender/gpu/vulkan/vk_context.hh | 5 + 9 files changed, 270 insertions(+) diff --git a/source/blender/gpu/GPU_debug.h b/source/blender/gpu/GPU_debug.h index 198dddc9f6c..f30274d45aa 100644 --- a/source/blender/gpu/GPU_debug.h +++ b/source/blender/gpu/GPU_debug.h @@ -29,6 +29,56 @@ void GPU_debug_get_groups_names(int name_buf_len, char *r_name_buf); */ bool GPU_debug_group_match(const char *ref); +/** GPU Frame capture support. + * Allows instananeous Frame capture of GPU calls between begin/end. */ +void GPU_debug_capture_begin(void); +void GPU_debug_capture_end(void); + +/** GPU debug frae capture scopes. + * Allows creation of a GPU Frame Capture scope that define a region within which an external GPU + * Frame capture tool can perform a deferred capture of GPU API calls within the boundary upon user + * request. */ + +/* Returns a pointer wrapping a n API-specific capture scope. + * A capture scope should be created a single time and only used within one begin/end pair.*/ +void *GPU_debug_capture_scope_create(const char *name); + +/* Used to declare the region within which GPU calls are captured when the scope is triggered. + * These functions will return true if the desired tool is actively capturing this scope when + * executed. Otherwise, false.*/ +bool GPU_debug_capture_scope_begin(void *scope); +void GPU_debug_capture_scope_end(void *scope); + +/** GPU Debug Capture Usage Example + * + ** Instant frame capture. ** + * + * #include "GPU_debug.h" + * static void do_render_engine(Render *re) + * { + * GPU_debug_capture_begin(); + * RE_engine_render(re, false); + * GPU_debug_capture_end(); + * } + * + * + ** Capture Scopes. ** + * + * void *capture_scope = nullptr; + * static void do_render_engine(Render *re) + * { + * if (!capture_scope) { + * // Create capture scope which will display in external tool. + * capture_scope = GPU_debug_capture_scope_create("Render Frame"); + * } + * + * // Commands within scope boundary captured when requested in tool. + * GPU_debug_capture_scope_begin(capture_scope); + * RE_engine_render(re, false); + * GPU_debug_capture_scope_end(capture_scope); + * } + */ + #ifdef __cplusplus } #endif diff --git a/source/blender/gpu/intern/gpu_context_private.hh b/source/blender/gpu/intern/gpu_context_private.hh index 2217e5262ed..73791a5c54b 100644 --- a/source/blender/gpu/intern/gpu_context_private.hh +++ b/source/blender/gpu/intern/gpu_context_private.hh @@ -47,6 +47,7 @@ class Context { FrameBuffer *front_right = nullptr; DebugStack debug_stack; + bool debug_is_capturing = false; /* GPUContext counter used to assign a unique ID to each GPUContext. * NOTE(Metal): This is required by the Metal Backend, as a bug exists in the global OS shader @@ -84,6 +85,13 @@ class Context { virtual void debug_group_begin(const char *, int){}; virtual void debug_group_end(){}; + /* Returns true if capture successfully started. */ + virtual bool debug_capture_begin() = 0; + virtual void debug_capture_end() = 0; + virtual void *debug_capture_scope_create(const char *name) = 0; + virtual bool debug_capture_scope_begin(void *scope) = 0; + virtual void debug_capture_scope_end(void *scope) = 0; + bool is_active_on_thread(); }; diff --git a/source/blender/gpu/intern/gpu_debug.cc b/source/blender/gpu/intern/gpu_debug.cc index 055207eace8..79f78c1af5c 100644 --- a/source/blender/gpu/intern/gpu_debug.cc +++ b/source/blender/gpu/intern/gpu_debug.cc @@ -73,3 +73,96 @@ bool GPU_debug_group_match(const char *ref) } return false; } + +void GPU_debug_capture_begin() +{ + /* GPU Frame capture is only enabled when --debug-gpu is specified. */ + if (!(G.debug & G_DEBUG_GPU)) { + return; + } + + Context *ctx = Context::get(); + if (ctx && !ctx->debug_is_capturing) { + ctx->debug_is_capturing = ctx->debug_capture_begin(); + if (!ctx->debug_is_capturing) { + printf("Failed to start GPU frame capture!\n"); + } + /* Call GPU_finish to ensure all desired GPU commands occur within the capture boundary. */ + GPU_finish(); + } +} + +void GPU_debug_capture_end() +{ + /* GPU Frame capture is only enabled when --debug-gpu is specified. */ + if (!(G.debug & G_DEBUG_GPU)) { + return; + } + + Context *ctx = Context::get(); + if (ctx && ctx->debug_is_capturing) { + /* Call GPU_finish to ensure all desired GPU commands occur within the capture boundary. */ + GPU_finish(); + ctx->debug_capture_end(); + ctx->debug_is_capturing = false; + } +} + +void *GPU_debug_capture_scope_create(const char *name) +{ + /* GPU Frame capture is only enabled when --debug-gpu is specified. */ + if (!(G.debug & G_DEBUG_GPU)) { + return NULL; + } + + Context *ctx = Context::get(); + if (!ctx) { + return NULL; + } + return ctx->debug_capture_scope_create(name); +} + +bool GPU_debug_capture_scope_begin(void *scope) +{ + /* Early exit if scope does not exist or not in debug mode. */ + if (!(G.debug & G_DEBUG_GPU) || !scope) { + return false; + } + + Context *ctx = Context::get(); + if (!ctx) { + return false; + } + + /* Declare beginning of capture scope region. */ + bool scope_capturing = ctx->debug_capture_scope_begin(scope); + if (scope_capturing && !ctx->debug_is_capturing) { + /* Call GPU_finish to ensure all desired GPU commands occur within the capture boundary. */ + GPU_finish(); + ctx->debug_is_capturing = true; + } + return ctx->debug_is_capturing; +} + +void GPU_debug_capture_scope_end(void *scope) +{ + /* Early exit if scope does not exist or not in debug mode. */ + if (!(G.debug & G_DEBUG_GPU) || !scope) { + return; + } + + Context *ctx = Context::get(); + if (!ctx) { + return; + } + + /* If capturing, call GPU_finish to ensure all desired GPU commands occur within the capture + * boundary. */ + if (ctx->debug_is_capturing) { + GPU_finish(); + ctx->debug_is_capturing = false; + } + + /* Declare end of capture scope region. */ + ctx->debug_capture_scope_end(scope); +} diff --git a/source/blender/gpu/metal/mtl_context.hh b/source/blender/gpu/metal/mtl_context.hh index 3523cd47614..80103987fca 100644 --- a/source/blender/gpu/metal/mtl_context.hh +++ b/source/blender/gpu/metal/mtl_context.hh @@ -740,6 +740,11 @@ class MTLContext : public Context { void debug_group_begin(const char *name, int index) override; void debug_group_end() override; + bool debug_capture_begin() override; + void debug_capture_end() override; + void *debug_capture_scope_create(const char *name) override; + bool debug_capture_scope_begin(void *scope) override; + void debug_capture_scope_end(void *scope) override; /*** MTLContext Utility functions. */ /* diff --git a/source/blender/gpu/metal/mtl_debug.mm b/source/blender/gpu/metal/mtl_debug.mm index 8ca4a0cc6e3..26f0227b56c 100644 --- a/source/blender/gpu/metal/mtl_debug.mm +++ b/source/blender/gpu/metal/mtl_debug.mm @@ -57,6 +57,64 @@ void MTLContext::debug_group_end() } } +bool MTLContext::debug_capture_begin() +{ + MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager]; + if (!capture_manager) { + /* Early exit if frame capture is disabled. */ + return false; + } + MTLCaptureDescriptor *capture_descriptor = [[MTLCaptureDescriptor alloc] init]; + capture_descriptor.captureObject = this->device; + NSError *error; + if (![capture_manager startCaptureWithDescriptor:capture_descriptor error:&error]) { + NSLog(@"Failed to start Metal frame capture, error %@", error); + return false; + } + return true; +} + +void MTLContext::debug_capture_end() +{ + MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager]; + if (!capture_manager) { + /* Early exit if frame capture is disabled. */ + return; + } + [capture_manager stopCapture]; +} + +void *MTLContext::debug_capture_scope_create(const char *name) +{ + /* Create a capture scope visible to xCode Metal Frame capture utility. */ + MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager]; + if (!capture_manager) { + /* Early exit if frame capture is disabled. */ + return nullptr; + } + id capture_scope = [capture_manager newCaptureScopeWithDevice:this->device]; + capture_scope.label = [NSString stringWithUTF8String:name]; + [capture_scope retain]; + + return reinterpret_cast(capture_scope); +} + +bool MTLContext::debug_capture_scope_begin(void *scope) +{ + /* Declare opening boundary of scope. + * When scope is selected for capture, GPU commands between begin/end scope will be captured. */ + [(id)scope beginScope]; + + MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager]; + return [capture_manager isCapturing]; +} + +void MTLContext::debug_capture_scope_end(void *scope) +{ + [(id)scope endScope]; + MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager]; +} + /** \} */ } // namespace blender::gpu diff --git a/source/blender/gpu/opengl/gl_context.hh b/source/blender/gpu/opengl/gl_context.hh index 1d413750fd4..2d19bc9a10d 100644 --- a/source/blender/gpu/opengl/gl_context.hh +++ b/source/blender/gpu/opengl/gl_context.hh @@ -134,6 +134,11 @@ class GLContext : public Context { void debug_group_begin(const char *name, int index) override; void debug_group_end() override; + bool debug_capture_begin() override; + void debug_capture_end() override; + void *debug_capture_scope_create(const char *name) override; + bool debug_capture_scope_begin(void *scope) override; + void debug_capture_scope_end(void *scope) override; private: static void orphans_add(Vector &orphan_list, std::mutex &list_mutex, GLuint id); diff --git a/source/blender/gpu/opengl/gl_debug.cc b/source/blender/gpu/opengl/gl_debug.cc index 5c23286c166..ee6b12d6676 100644 --- a/source/blender/gpu/opengl/gl_debug.cc +++ b/source/blender/gpu/opengl/gl_debug.cc @@ -380,6 +380,29 @@ void GLContext::debug_group_end() } } +bool GLContext::debug_capture_begin() +{ + return false; +} + +void GLContext::debug_capture_end() +{ +} + +void *GLContext::debug_capture_scope_create(const char *name) +{ + return nullptr; +} + +bool GLContext::debug_capture_scope_begin(void *scope) +{ + return false; +} + +void GLContext::debug_capture_scope_end(void *scope) +{ +} + /** \} */ } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_context.cc b/source/blender/gpu/vulkan/vk_context.cc index ca03073313b..c362e6b53b2 100644 --- a/source/blender/gpu/vulkan/vk_context.cc +++ b/source/blender/gpu/vulkan/vk_context.cc @@ -128,4 +128,27 @@ void VKContext::debug_group_end() { } +bool VKContext::debug_capture_begin() +{ + return false; +} + +void VKContext::debug_capture_end() +{ +} + +void *VKContext::debug_capture_scope_create(const char *name) +{ + return nullptr; +} + +bool VKContext::debug_capture_scope_begin(void *scope) +{ + return false; +} + +void VKContext::debug_capture_scope_end(void *scope) +{ +} + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_context.hh b/source/blender/gpu/vulkan/vk_context.hh index 912ab1e8dd9..b646492faec 100644 --- a/source/blender/gpu/vulkan/vk_context.hh +++ b/source/blender/gpu/vulkan/vk_context.hh @@ -49,6 +49,11 @@ class VKContext : public Context { void debug_group_begin(const char *, int) override; void debug_group_end() override; + bool debug_capture_begin() override; + void debug_capture_end() override; + void *debug_capture_scope_create(const char *name) override; + bool debug_capture_scope_begin(void *scope) override; + void debug_capture_scope_end(void *scope) override; static VKContext *get(void) { -- 2.30.2 From c7db9faf9b55dc2a19472dbc41a4cc0289a7dac8 Mon Sep 17 00:00:00 2001 From: Michael Parkin-White Date: Tue, 14 Mar 2023 20:14:10 +0000 Subject: [PATCH 2/2] Improve code documentation for GPU_debug capture functions. --- source/blender/gpu/GPU_debug.h | 97 ++++++++++++++++----------- source/blender/gpu/metal/mtl_debug.mm | 1 - 2 files changed, 56 insertions(+), 42 deletions(-) diff --git a/source/blender/gpu/GPU_debug.h b/source/blender/gpu/GPU_debug.h index f30274d45aa..043c39a5132 100644 --- a/source/blender/gpu/GPU_debug.h +++ b/source/blender/gpu/GPU_debug.h @@ -5,6 +5,40 @@ * \ingroup gpu * * Helpers for GPU / drawing debugging. + * + * + ** GPU debug capture usage example: + * + ** Instant frame capture. ** + * + * \code + * #include "GPU_debug.h" + * static void do_render_engine(Render *re) + * { + * GPU_debug_capture_begin(); + * RE_engine_render(re, false); + * GPU_debug_capture_end(); + * } + * \endcode + * + ** Capture scopes. ** + * + * \code + * #include "GPU_debug.h" + * void *capture_scope = nullptr; + * static void do_render_engine(Render *re) + * { + * if (!capture_scope) { + * // Create capture scope which will display in external tool. + * capture_scope = GPU_debug_capture_scope_create("Render Frame"); + * } + * + * // Commands within scope boundary captured when requested in tool. + * GPU_debug_capture_scope_begin(capture_scope); + * RE_engine_render(re, false); + * GPU_debug_capture_scope_end(capture_scope); + * } + * \endcode */ #pragma once @@ -29,56 +63,37 @@ void GPU_debug_get_groups_names(int name_buf_len, char *r_name_buf); */ bool GPU_debug_group_match(const char *ref); -/** GPU Frame capture support. - * Allows instananeous Frame capture of GPU calls between begin/end. */ +/** + * GPU Frame capture support. + * + * Allows instananeous frame capture of GPU calls between begin/end. + */ void GPU_debug_capture_begin(void); void GPU_debug_capture_end(void); -/** GPU debug frae capture scopes. - * Allows creation of a GPU Frame Capture scope that define a region within which an external GPU +/** + * GPU debug frame capture scopes. + * + * Allows creation of a GPU frame capture scope that define a region within which an external GPU * Frame capture tool can perform a deferred capture of GPU API calls within the boundary upon user - * request. */ - -/* Returns a pointer wrapping a n API-specific capture scope. - * A capture scope should be created a single time and only used within one begin/end pair.*/ + * request. + * + * \param name Unique name of capture scope displayed within capture tool. + * \return pointer wrapping an API-specific capture scope object. + * \note a capture scope should be created a single time and only used within one begin/end pair. + */ void *GPU_debug_capture_scope_create(const char *name); -/* Used to declare the region within which GPU calls are captured when the scope is triggered. - * These functions will return true if the desired tool is actively capturing this scope when - * executed. Otherwise, false.*/ +/** + * Used to declare the region within which GPU calls are captured when the scope is triggered. + * + * \param scope Pointer to capture scope object created with GPU_debug_capture_scope_create. + * \return True if the capture tool is actively capturing this scope when function is executed. + * Otherwise, False. + */ bool GPU_debug_capture_scope_begin(void *scope); void GPU_debug_capture_scope_end(void *scope); -/** GPU Debug Capture Usage Example - * - ** Instant frame capture. ** - * - * #include "GPU_debug.h" - * static void do_render_engine(Render *re) - * { - * GPU_debug_capture_begin(); - * RE_engine_render(re, false); - * GPU_debug_capture_end(); - * } - * - * - ** Capture Scopes. ** - * - * void *capture_scope = nullptr; - * static void do_render_engine(Render *re) - * { - * if (!capture_scope) { - * // Create capture scope which will display in external tool. - * capture_scope = GPU_debug_capture_scope_create("Render Frame"); - * } - * - * // Commands within scope boundary captured when requested in tool. - * GPU_debug_capture_scope_begin(capture_scope); - * RE_engine_render(re, false); - * GPU_debug_capture_scope_end(capture_scope); - * } - */ - #ifdef __cplusplus } #endif diff --git a/source/blender/gpu/metal/mtl_debug.mm b/source/blender/gpu/metal/mtl_debug.mm index 26f0227b56c..77637052f7b 100644 --- a/source/blender/gpu/metal/mtl_debug.mm +++ b/source/blender/gpu/metal/mtl_debug.mm @@ -112,7 +112,6 @@ bool MTLContext::debug_capture_scope_begin(void *scope) void MTLContext::debug_capture_scope_end(void *scope) { [(id)scope endScope]; - MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager]; } /** \} */ -- 2.30.2