GPU: Add GPU frame capture support. #105717

Merged
Jeroen Bakker merged 4 commits from Jason-Fielder/blender:GPU_frame_capture_support_2 into main 2023-03-16 08:54:18 +01:00
9 changed files with 284 additions and 0 deletions

View File

@ -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.

API docs although not really specified in https://wiki.blender.org/wiki/Style_Guide/C_Cpp we normally
open and close the comment on its own line eg:

/**
 * GPU Frame capture support.
 *
 * Allows instantaneous Frame capture of GPU calls between begin/end.
 */

In this case, we should use Comment sections and add API docs per function.

API docs although not really specified in https://wiki.blender.org/wiki/Style_Guide/C_Cpp we normally open and close the comment on its own line eg: ``` /** * GPU Frame capture support. * * Allows instantaneous Frame capture of GPU calls between begin/end. */ ``` In this case, we should use Comment sections and add API docs per function.
* 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);

Spelling: frame

Spelling: frame
* RE_engine_render(re, false);
* GPU_debug_capture_scope_end(capture_scope);
* }
* \endcode
*/
#pragma once
@ -29,6 +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.
Review

instantaneous

instan**t**aneous
*/
void GPU_debug_capture_begin(void);
void GPU_debug_capture_end(void);
/**
* 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.
*
* \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.
*
* \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);
#ifdef __cplusplus
}
#endif

View File

@ -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();
};

View File

@ -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);
}

View File

@ -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. */
/*

View File

@ -57,6 +57,63 @@ 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<MTLCaptureScope> capture_scope = [capture_manager newCaptureScopeWithDevice:this->device];
capture_scope.label = [NSString stringWithUTF8String:name];
[capture_scope retain];
return reinterpret_cast<void *>(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<MTLCaptureScope>)scope beginScope];
MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager];
return [capture_manager isCapturing];
}
void MTLContext::debug_capture_scope_end(void *scope)
{
[(id<MTLCaptureScope>)scope endScope];
}
/** \} */
} // namespace blender::gpu

View File

@ -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<GLuint> &orphan_list, std::mutex &list_mutex, GLuint id);

View File

@ -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

View File

@ -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

View File

@ -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)
{