floooh / sokol

minimal cross-platform standalone C headers

Home Page:https://floooh.github.io/sokol-html5

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Ability to label command encoders

danielchasehooper opened this issue · comments

Sokol uses the word pass for two things:

  • pass as render destination; created with sg_make_pass
  • pass as series of commands; created with sg_pass_begin

Currently you can label the first kind of pass and that shows up in Metal GPU Debugger, but there is no way to label the second kind of pass, which would be very useful when debugging.

If you're open to it, I'd be happy to submit a PR that adds a char *label member to sg_pass_action and uses that to populate MTLRenderCommandEncoder.label and the the other APIs' equivalents, if applicable.

In our project, we implemented the below function as a stopgap, and it's super useful. However, I think it'd be more elegant if integrated into sg_pass_action

void sg_pass_label(char *label) {
#if defined(SOKOL_METAL)
    SOKOL_ASSERT(_sg.mtl.in_pass);
    if(!_sg.mtl.pass_valid) return;

    id<MTLRenderCommandEncoder> encoder = _sg.mtl.cmd_encoder;
    encoder.label = [[NSString alloc] initWithBytesNoCopy:label length:strlen(label) encoding:NSUTF8StringEncoding freeWhenDone:NO];
#endif
}

How the label appears in the GPU Debugger

Screenshot 2024-01-22 at 11 25 20 AM

and
Screenshot 2024-01-22 at 11 19 50 AM

Yes that sounds like a good fix. If you prepare a PR I can merge that quickly.

This looks a bit concerning though in the NSString creation: initWithBytesNoCopy:label. This looks to me like the NSString object is pointing into the original string data (which would work fine if the label is a static C string, but not otherwise).

For something as low-frequency as a pass it's ok to have the NSString copy the string data over.

I wonder though... let me check something one sec...

I think I have a better idea... sg_pass_desc already has a label field, but that isn't used in _sg_mtl_create_pass() (because Metal has no "pass resource objects").

Here's my suggestion:

Store the label in the _sg_mtl_pass_t struct, e.g. change _sg_mtl_pass_t like this:

typedef struct {
    _sg_slot_t slot;
    _sg_pass_common_t cmn;
    struct {
        _sg_mtl_attachment_t color_atts[SG_MAX_COLOR_ATTACHMENTS];
        _sg_mtl_attachment_t resolve_atts[SG_MAX_COLOR_ATTACHMENTS];
        _sg_mtl_attachment_t ds_att;
        _sg_str_t label;
    } mtl;
} _sg_mtl_pass_t

...in _sg_mtl_create_pass() copy the label string over (sg_strcpy is "safe" in that it will make sure to not overrun the buffer in _sg_str_t):

    #if defined(SOKOL_DEBUG)
        if (desc->label) {
            _sg_strcpy(&pass->mtl.label, desc->label);
        }
    #endif

...then in _sg_mtl_begin_pass(), when it's the default pass, use a hardwired "default" label string, otherwise use the string that's stored in the pass object.

If 15 characters for the label isn't enough we can bump _SG_STRING_SIZE (the current max size is probably a bit tight also for the other places where strings are stored).

I think I have a better idea... sg_pass_desc already has a label field, but that isn't used in _sg_mtl_create_pass() (because Metal has no "pass resource objects").

The way I label my pass-as-render-target is not how I want to label my pass-as-series-of-commands (command encoder in metal parlance).

Here is an example of why you want to be able to label a command encoder differently from the texture it's rendering into. Say we have code to perform a gaussian blur that requires 2 intermediate framebuffers and 4 distinct steps:

source framebuffer -> "downscale" -> framebuffer 1
framebuffer 1 -> "blur horizontally" -> framebuffer 2
framebuffer 2 -> "blur vertically" -> framebuffer 1
framebuffer 1 -> "upscale" -> destination framebuffer

In the gpu debugger I would like to see the command encoders for this example scenario labeled as "downscale","blur horizontally", "blur vertically", and "upscale". If sg_begin_pass used the label of the destination sg_pass, then you would see "framebuffer 1"/"framebuffer 2" over and over in the debugger.

In the first image in my above comment, naming encoders based on the sg_pass label would cause all the orange boxes to be labeled as "Surface image.0" – totally unhelpful. Additionally, You can see in my second screenshot above that it is valuable for the render targets (green checkerboard icon) to have separate names from the render encoders (orange picture icons).

I think the double meaning of the word "pass" in sokol_gfx may be a source of confusion. Where you're rendering into and what you're rendering to it are different, but have the same name in sokol's types.

Yeah makes sense. In that case I would prefer to integrate the fix with #904, since sg_pass_action also isn't quite the right place IMHO.

With those 'unified passes', the sg_begin_pass struct would take a new wrapper struct as argument, and this would be the right place for the label.

E.g. for an offscreen-pass:

    sg_begin_pass(&(sg_tbd){
        .pass = ....,
        .pass_action = { ... },
        .label = "bla",
    });

...and for a "swapchain pass" (don't know yet if I'll call it that):

    sg_begin_pass(&(sg_tbd){
        .swapchain = sapp_sgswapchain(...), // helper function from sokol_glue.h
        .pass_action = { ... },
        .label = "blub",
    });

Does that make sense?

sure, however you want to package the functionality.

One thing worth considering: I suspect #904 is a long way away from shipping. I can see value in adding label to sg_pass_action now so that people can benefit from this straightforward non-breaking change. Since #904 is a breaking change anyway, the label member can be moved wherever is deemed appropriate at that time.

I'm planning to tackle #904 after taking care of a couple of smaller issues and PRs. At some point I need to get back into 'feature mode' anyway ;)

This is now also fixed since #985 has been merged.