Skip to content

Add CaptureMask support - #34

Draft
ccp-chargeback wants to merge 42 commits into
carbonengine:mainfrom
ccp-chargeback:capture_mask_support
Draft

Add CaptureMask support#34
ccp-chargeback wants to merge 42 commits into
carbonengine:mainfrom
ccp-chargeback:capture_mask_support

Conversation

@ccp-chargeback

@ccp-chargeback ccp-chargeback commented Jul 17, 2026

Copy link
Copy Markdown
Member

Change includes:

  • The ability of a component to register for a CaptureMask, with or without specifying a color
  • If registering a CaptureMask without specifying a color, a color will be automatically chosen.
  • Get a list of Registered CaptureMasks containing their name, assigned maskBit and color
  • Set the Active CaptureMask, meaning what components are tracked in a Telemetry session
  • The active CaptureMask can be set either before or during an active Telemetry session.
  • An active CaptureMask can be defined either by a bitmask or a list of "component names"
  • A yet to be registered component from SetActiveCaptureMask(listOfNames) will be added to a "pending" list until the component is registered.
  • Get the combined bitmask of the active CaptureMasks (excluding the "pending" ones)
  • Add overloads for both: CcpTelemetryEnterZone() and the TelemetryZone constructor, that accept captureMaskBit as argument

Change contains:
- Ability to call RegisterCaptureMask() for a given name and an optional Telemetry Zone display color
- Automatically assign a color if one is not provided

Part of:
https://fenriscreations.atlassian.net/browse/PLAT-11474
Changed:
- enum class Color => CcpColor
- namespace CcpColor => ColorUtil
Change includes:
- Expose CcpGetRegisteredCaptureMasks() from core
- Add/change test coverage for CaptureMasks tests
- Rename existing CaptureMasks variables to state they are for "registered" CaptureMasks

Part of:
https://fenriscreations.atlassian.net/browse/PLAT-11475
Will give us the option of a O(1) lookup for display color once we change telemetry to mark zones based on CaptureMask bit.
Change contains:
- Two overloads of CcpSetActiveCaptureMask() function
- An exported CcpGetActiveCaptureMask() function
- Support for an "all" active CaptureMask
- Support for lazy-register of active CaptureMasks by name via a list of pending ones.

Part of:
https://fenriscreations.atlassian.net/browse/PLAT-11481
Make sure we can verify the color associated with a Zone from the TracyTestClient in tests.
Done because of build problems on v143/v145 vs v141 identified by ccptoebeans.
Add CaptureMaksBit overloads for:
- TelemetryZone constructor
- CcpTelemetryEnterZone()
Make sure tests reflect the new reality.

Part of:
https://fenriscreations.atlassian.net/browse/PLAT-11476
@ccp-chargeback
ccp-chargeback requested review from CCP-Aporia and ccp-serpent and removed request for CCP-Aporia and ccp-serpent July 17, 2026 15:31
@ccpgames-carbon

ccpgames-carbon commented Jul 20, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Legal hasn't advised on an updated statement yet, so we stick with `CCP ehf.`.
…es for a given color value

To avoid unnecessary memory copies of the underlying string data, and to avoid surprises in seeing `CcpColorToString( CcpColor::Cyan )` return `Aqua`.
Instead, have `Color` as a default-parameter that is piped through. This alone shouldn't have required a separately overloaded implementation.

Also, stop lower-casing the input name, because it causes a mismatch between what is passed into the system as "display name" versus what comes back out of the system.

Additionally, remove the `all` special case handling. Enabling all capture masks can be done by passing in all capture masks. Having a sentinel value that represents a possible legal value is bound to cause surprising behaviour down the line. Sentinel values should be flagged explicitly, e.g. an alternative would have been to have an explicit function like `CcpCaptureEverything()` function.

Furthermore, this simplifies initialization of pre-registered captureMasks: why run a lambda function during static initialization stage when we have initializer lists?
For type-safe constants instead of preprocessor text replacement.
The order of registration doesn't matter to any users of `CcpRegisterCaptureMask`, primarily because that order depends on the order of calls to that function, not the implementation itself.
This is for an upcoming QoL improvement for capture mask users, where they should no longer have to rely on the implementation of using a bitmask for the capture mask.
Since the bitmask is only an implementation details, and because `CcpSetActiveCaptureMask` takes in the names, too.
This avoids double-checking whether we can access memory safely further down the test. If we only use EXPECT_* macros some tests may crash if the observed container is empty.
…I compatibility

ABI is preserved with the overload as well, since both the `uint32_t` and `uint64_t` version have their distinct mangled names exported. So just dropping in the new library should be just fine.
Of course, what it does mean is that downstream consumers of core will need to make some fixes to their code when they get _recompiled_ but that is not breaking _ABI_.
That said, semantic versioning is also about the public API. So we are facing a decision here:
1. Accept that we need yet another major version tag for Core
2. Introduce terrible developer ergonomics where anyone who wants to construct the right version of `TelemetryZone` needs to pass in a really awkward sentinel value as first parameter.
3. Alternately, define a `TelemetryZoneWithCaptureMask` subclass that takes the `uint64_t` constructor without requiring the sentinel constant.
4. Last, but not least, change the way that capture mask registration works. Instead of "leaking" the bit mask implementation details through to the caller, we can return the index into the array of registered masks. That's still leaking an implementation details, but we can make that intent clear by calling it a "CaptureMaskHandle" or something similar.

I'd argue that we should settle for option 4. Option 1 is annoying because just tagged a new major version for a subset of the telemetry improvements. Option 2 is terrible ergonomics on the call side. Option 3 complicates the API surface: When to use which one? OK, one is deprecated, that gives a hint, but the deprecation also implies that we _are_ going to break ABI and API again in the future. So we may as well just take the pain now.
Option 4, on the other hand, does not require changes to function signatures, and therefore retains both ABI and API compatibility. It also means we no longer have to do a reverse-lookup of bitmask to color for a registered capture mask.

So this commit is the first step in that direction of option 4.
…capture mask

Users of the telemetry instrumentation should pass this handle on when creating a `TelemetryZone` if they want it to be captured as part of their registered mask.

Implementation details: This currently represents the index into the array of registered capture masks, as that keeps the look up of name and color cheap.
Comment thread CcpTelemetry.cpp Outdated
Comment thread CcpTelemetry.cpp Outdated
Comment thread CcpTelemetry.cpp Outdated
Comment thread CcpTelemetry.cpp Outdated
Comment thread include/CcpTelemetry.h Outdated
Comment on lines +50 to +56
// - An active CaptureMask defaults to "all" but can be set/narrowed before or
// during a Telemetry session is started using either:
// - a numerical bit mask value of the active CaptureMask
// - list of "component display names" ("all" is allowed)
// - Setting active CaptureMask is available for both:
// - already registered components
// - "pending" (yet to be registered) components

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads like it should be part of the general documentation for our telemetry integration, as it's not specific at all to the CcpCaptureMaskInfo structure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. We should reserve comment blocks like this for classes and symbols we ourselves define, rather than explaining external concepts like what a capture mask is. Top-down explanations for how our systems operate should be included in general documentation as well.

Comment thread include/CcpTelemetry.h Outdated

CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name );
CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color );
CARBON_CORE_API std::vector<CcpCaptureMaskInfo> CcpGetRegisteredCaptureMasks();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not return a const reference to the built-in capture mask array?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the array contains reserved entries as well as all the registered capture masks.

Comment thread include/CcpTelemetry.h Outdated
Comment thread CcpTelemetry.cpp Outdated
Comment thread include/CcpTelemetry.h Outdated
public:
TelemetryZone() = delete;
CARBON_CORE_API TelemetryZone( uint32_t ctx, const char* name, const char* filename, uint32_t lineno, CcpColor color = CcpColor::SteelBlue );
CARBON_CORE_API TelemetryZone( CcpCaptureMaskHandle handle, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete = CcpColor::SteelBlue );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because CcpCaptureMaskHandle is a uint32_t then this should be both ABI and API compatible. Need to verify. In worst case, this stays the non-specialised type for the time being.

There is only one capture mask from the user's perspective, so let's keep implementation details out of the function name.
There were two issues at play here:
1. The same iterator into the `FiberNameStore` may have been queued for erasure multiple times
2. Inserting a name into the `FiberNameStore` that has a pending erasure would

Surprisingly, those issues primarily surfaced on macOS, and only rarely on Windows.

The solution to the problem is to maintain a map instead of a queue. This way, inserting into `FiberNameStore` can cancel any pending erasure. Additionally, the map ensures that there is only ever one entry for `FiberNameStore` iterator that should be deleted.

@ccp-serpent ccp-serpent left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are clear improvements which can be made to the documentation, and the code could do with some refactoring. At a functional level, however, I don't see any problems with the code as it's written.

What I'd like to draw particular attention to is how the API makes mentions of the CaptureMask concept. As the code is written now, a capture mask can be either a struct, a list of strings, a list of structs, when it's in fact a uint64_t type.

Take the following functions for example:

  • std::vector<CcpCaptureMaskInfo> CcpGetRegisteredCaptureMasks()
  • std::vector<std::string> CcpGetActiveCaptureMask()

There's only ever one capture mask active, yet the API reads like we may have multiple capture masks registered and active within our telemetry integration.

Neither of these functions return a capture mask. They return a list of names, which correspond to a profiling category which may be represented by a capture mask. In that sense, they´re not behaving as their name suggests.

This goes even further, see below:
CcpCaptureMaskHandle CcpRegisterCaptureMask( const std::string& name, CcpColor color = CcpColor::Fuchsia )

This function is not registering a capture mask. It's registering a profiler category which the code is later able to associate with a bit and incorporate into a capture mask.

These examples read like there's something missing at a conceptual level from the code. Or perhaps the functions just need to be renamed. I don't want to state which is which, but I believe this could stand improving.

Comment thread include/CcpTelemetry.h Outdated
Comment on lines +50 to +56
// - An active CaptureMask defaults to "all" but can be set/narrowed before or
// during a Telemetry session is started using either:
// - a numerical bit mask value of the active CaptureMask
// - list of "component display names" ("all" is allowed)
// - Setting active CaptureMask is available for both:
// - already registered components
// - "pending" (yet to be registered) components

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. We should reserve comment blocks like this for classes and symbols we ourselves define, rather than explaining external concepts like what a capture mask is. Top-down explanations for how our systems operate should be included in general documentation as well.

Comment thread include/CcpTelemetry.h
// ---------------------------------------------------------------------------
// CaptureMasks:
// - CaptureMask(s) are used to determine if a given Zone should be emitted to
// Telemetry tracking or not based on its origin, i.e. carbon component.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no hard association between capture masks and carbon components. A component may register multiple capture masks, for example, or even none. This reads a bit confused.

Comment thread include/CcpTelemetry.h
// - a numerical bit mask value of the active CaptureMask
// - list of "component display names" ("all" is allowed)
// - Setting active CaptureMask is available for both:
// - already registered components

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a struct. What is this concept of components already registered or pending registration? This documentation may be relevant to whatever function performs the registration, or the container for pending components. But I don't think these are useful concepts to explain in the context of a CcpCaptureMaskInfo struct.

Comment thread include/CcpTelemetry.h Outdated
// ---------------------------------------------------------------------------
struct CcpCaptureMaskInfo
{
std::string name; // The lower-case display name of the CaptureMask (carbon-component)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carbon components are not conceptually related to capture masks. I'm also not sure it's useful to document that the display name is in lower-case, unless the system requires display names to be lowercase, or the display name is converted to lowercase upon registration. Either case, that should probably be documented alongside the registration function for capture masks.

Comment thread include/CcpTelemetry.h Outdated
struct CcpCaptureMaskInfo
{
std::string name; // The lower-case display name of the CaptureMask (carbon-component)
CcpColor color{CcpColor::White}; // The chosen/allocated color for the CaptureMask

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The words "chosen" and "allocated" are not necessarily interchangeable. Does some external system allocate a display color to the mask info struct? I suggest documenting this with a simpler // Display color, further context can be added to code points utilizing the struct, if necessary.

Comment thread CcpTelemetry.cpp Outdated

bool CcpSetCaptureMask( const std::vector<std::string>& maskNames )
{
// Guard access to all CaptureMasks members

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread CcpTelemetry.cpp Outdated
Comment thread CcpTelemetry.cpp
uint64_t newActiveCaptureMask = 0;
for( const auto& rawName : maskNames )
{
if( rawName.empty() )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the code would read better if the contents of this for loop was extracted into a separate function, i.e. bool CcpAddCaptureMask(const std::string& name)

Comment thread include/CcpTelemetry.h Outdated

CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name );
CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color );
CARBON_CORE_API std::vector<CcpCaptureMaskInfo> CcpGetRegisteredCaptureMasks();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the array contains reserved entries as well as all the registered capture masks.

Comment thread tests/CcpTelemetry.cpp Outdated
Those two tests covered functionality that has been removed / collapsed into the same `TelemetryZone` constructor since then.
As per code-review feedback, the "mask" implementation details does no longer hold up across the API surface.
Reduces compile / link time when building without telemetry, and removes the need for the `#ifdef` in the test code.
Take note that currently building `WITH_TELEMETRY=OFF` does not work in general for core, unrelated to this work.
Without it, there's a risk of accessing bad memory when determining the color, and odd behaviour when checking the bit mask.
As per code review feedback. It's a better way to track existence without relying on a sentinel value that could theoretically be valid.
As opposed to the generic `std::string`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants