Skip to content

Commit

Permalink
Support external customization of frame buffer events presentation (#39)
Browse files Browse the repository at this point in the history
In the buffer tracks for SurfaceFlinger events, an application that wants
to customize their presentation has not a clean mechanism by which to
do so. This commit provides for relaxation of track registries to allow
previous registrations to be superseded and refactors the async slice
track a bit to support extension/customization by external plugins.

Signed-off-by: Christian W. Damus <[email protected]>
  • Loading branch information
cdamus authored Sep 27, 2023
1 parent bcbeb49 commit e5e05ae
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 19 deletions.
11 changes: 9 additions & 2 deletions ui/src/common/plugin_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ export interface PluginContext {
// 'TrackController' and a 'Track'. In more recent versions of the UI
// the functionality of |TrackController| has been merged into Track so
// |TrackController|s are not necessary in new code.
registerTrackController(track: TrackControllerFactory): void;
// Unless |supersede| is true, then an attempt to register a |track|
// controller factory for a |track| kind that is already registered will
// throw an error.
registerTrackController(track: TrackControllerFactory,
supersede?: boolean): void;

// Register a |TrackProvider|. |TrackProvider|s return |TrackInfo| for
// all potential tracks in a trace. The core UI selects some of these
Expand All @@ -79,7 +83,10 @@ export interface PluginContext {
// which returns GPU counter tracks. The counter track factory itself
// could be registered in dev.perfetto.CounterTrack - a whole
// different plugin.
registerTrack(track: TrackCreator): void;
// Unless |supersede| is true, then an attempt to register a |track|
// controller factory for a |track| kind that is already registered will
// throw an error.
registerTrack(track: TrackCreator, supersede?: boolean): void;

// Register a track or track group filter. When track filtering is
// enabled, the core UI determines via the registered filters which
Expand Down
16 changes: 8 additions & 8 deletions ui/src/common/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { TrackFilter, TrackGroupFilter, trackFilterRegistry } from '../controller/track_filter';
import {TrackFilter, TrackGroupFilter, trackFilterRegistry} from '../controller/track_filter';
import {Engine} from '../common/engine';
import {
TrackControllerFactory,
Expand All @@ -29,7 +29,7 @@ import {
} from './plugin_api';
import {Registry} from './registry';
import {Selection} from './state';
import { CustomButton, CustomButtonArgs, customButtonRegistry } from '../frontend/button_registry';
import {CustomButton, CustomButtonArgs, customButtonRegistry} from '../frontend/button_registry';

// Every plugin gets its own PluginContext. This is how we keep track
// what each plugin is doing and how we can blame issues on particular
Expand All @@ -46,20 +46,20 @@ export class PluginContextImpl implements PluginContext {

// ==================================================================
// The plugin facing API of PluginContext:
registerTrackController(track: TrackControllerFactory): void {
trackControllerRegistry.register(track);
registerTrackController(track: TrackControllerFactory,
supersede = false): void {
trackControllerRegistry.register(track, supersede);
}

registerTrack(track: TrackCreator): void {
trackRegistry.register(track);
registerTrack(track: TrackCreator, supersede = false): void {
trackRegistry.register(track, supersede);
}

registerTrackProvider(provider: TrackProvider) {
this.trackProviders.push(provider);
}

registerTrackFilter(filter: TrackFilter | TrackGroupFilter): void {
trackFilterRegistry.register(filter);
trackFilterRegistry.register(filter);
}

registerOnDetailsPanelSelectionChange(
Expand Down
6 changes: 4 additions & 2 deletions ui/src/common/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ export class Registry<T> {
this.key = key;
}

register(registrant: T) {
// Register a |registrant| with the option to |supersede|
// an existing registrant for the same key, if any.
register(registrant: T, supersede = false) {
const kind = this.key(registrant);
if (this.registry.has(kind)) {
if (!supersede && this.registry.has(kind)) {
throw new Error(`Registrant ${kind} already exists in the registry`);
}
this.registry.set(kind, registrant);
Expand Down
24 changes: 23 additions & 1 deletion ui/src/frontend/hsluv_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {hsluvToHex} from 'hsluv';
import {hexToHsluv, hsluvToHex} from 'hsluv';

class HsluvCache {
storage = new Map<number, string>();
Expand All @@ -37,3 +37,25 @@ export function cachedHsluvToHex(
hue: number, saturation: number, lightness: number): string {
return cache.get(hue, saturation, lightness);
}

// A mapping of slice colors to contrasting colors
// suitable for rendering text on them. Keys and
// values are both hex codes of the form #rrggbb.
const contrastingTextColorCodeCache = new Map<string, string>();

// Obtain a color code contrasting to the given |color|
// that is suitable for painting text on it.
// @param color hex code in the form #rrggbb of
// something like a slice rendered in the track
// @returns a color hex code for contrasting text,
// which will either be white for a dark |color|
// or a dark grey-black for a light |color|
export function contrastingTextColorCode(color: string): string {
let result = contrastingTextColorCodeCache.get(color);
if (!result) {
const lightness = hexToHsluv(color)[2];
result = lightness > 65 ? '#202020' : '#ffffff';
contrastingTextColorCodeCache.set(color, result);
}
return result;
}
2 changes: 1 addition & 1 deletion ui/src/tracks/async_slices/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface Data extends TrackData {
isIncomplete: Uint16Array;
}

class AsyncSliceTrackController extends TrackController<Config, Data> {
export class AsyncSliceTrackController extends TrackController<Config, Data> {
static readonly kind = ASYNC_SLICE_TRACK_KIND;
private maxDurNs: TPDuration = 0n;

Expand Down
34 changes: 29 additions & 5 deletions ui/src/tracks/chrome_slices/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ export interface Data extends TrackData {
cpuTimeRatio?: Float64Array;
}

// Like a Slice, but only of the instantaneous kind and
// only enough information to render it in the track
export interface Instant {
title: string;
color: string;
tmAscent: number;
}

export class ChromeSliceTrackController extends TrackController<Config, Data> {
static kind = SLICE_TRACK_KIND;
private maxDurNs: TPDuration = 0n;
Expand Down Expand Up @@ -197,7 +205,9 @@ export class ChromeSliceTrack extends Track<Config, Data> {
ctx.textAlign = 'center';

// measuretext is expensive so we only use it once.
const charWidth = ctx.measureText('ACBDLqsdfg').width / 10;
const charTM = ctx.measureText('ACBDLqsdfg');
const charWidth = charTM.width / 10;
const charAscent = charTM.actualBoundingBoxAscent;

// The draw of the rect on the selected slice must happen after the other
// drawings, otherwise it would result under another rect.
Expand Down Expand Up @@ -253,6 +263,12 @@ export class ChromeSliceTrack extends Track<Config, Data> {
// D B
// Then B, C, D and back to A:
if (isInstant) {
const instant: Instant = {
title,
color,
tmAscent: charAscent,
};

if (isSelected) {
drawRectOnSelected = () => {
ctx.save();
Expand All @@ -264,19 +280,19 @@ export class ChromeSliceTrack extends Track<Config, Data> {
ctx.scale(INNER_CHEVRON_SCALE, INNER_CHEVRON_SCALE);
ctx.fillStyle = cachedHsluvToHex(hue, 100, 10);

this.drawChevron(ctx);
this.drawChevron(ctx, instant);
ctx.restore();

// Draw inner chevron as interior
ctx.fillStyle = color;
this.drawChevron(ctx);
this.drawChevron(ctx, instant);

ctx.restore();
};
} else {
ctx.save();
ctx.translate(rect.left, rect.top);
this.drawChevron(ctx);
this.drawChevron(ctx, instant);
ctx.restore();
}
continue;
Expand Down Expand Up @@ -325,7 +341,15 @@ export class ChromeSliceTrack extends Track<Config, Data> {
drawRectOnSelected();
}

drawChevron(ctx: CanvasRenderingContext2D) {
// Draw a chevron for the current intantaneous slice in the track.
// The optional |Instant| provides additional hints that may be
// useful in rendering the slice.
//
// Precondition: the calling context must first |save| the |ctx| because this
// method is free to change fill style, transform, and other properties of
// the context. And the caller must |restore| the |ctx| on return from this
// method to clear any such changes.
drawChevron(ctx: CanvasRenderingContext2D, _instant?: Instant) {
// Draw a chevron at a fixed location and size. Should be used with
// ctx.translate and ctx.scale to alter location and size.
ctx.beginPath();
Expand Down

0 comments on commit e5e05ae

Please sign in to comment.