xref: /aosp_15_r20/external/angle/src/libANGLE/gen_overlay_widgets.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1#! /usr/bin/python3
2
3# Copyright 2019 The ANGLE Project Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6#
7# gen_overlay_widgets.py:
8#  Code generation for overlay widgets.  Should be run when the widgets declaration file,
9#  overlay_widgets.json, is changed.
10#  NOTE: don't run this script directly. Run scripts/run_code_generation.py.
11
12import json
13import os
14import sys
15
16OUT_SOURCE_FILE_NAME = 'Overlay_autogen.cpp'
17OUT_HEADER_FILE_NAME = 'Overlay_autogen.h'
18
19IN_JSON_FILE_NAME = 'overlay_widgets.json'
20
21OUT_SOURCE_FILE_TEMPLATE = u"""// GENERATED FILE - DO NOT EDIT.
22// Generated by {script_name} using data from {input_file_name}.
23//
24// Copyright 2019 The ANGLE Project Authors. All rights reserved.
25// Use of this source code is governed by a BSD-style license that can be
26// found in the LICENSE file.
27//
28// {out_file_name}:
29//   Autogenerated overlay widget declarations.
30
31#include "libANGLE/renderer/driver_utils.h"
32#include "libANGLE/Overlay.h"
33#include "libANGLE/OverlayWidgets.h"
34#include "libANGLE/Overlay_font_autogen.h"
35
36namespace gl
37{{
38using namespace overlay;
39
40namespace
41{{
42int GetFontSize(int fontSize, bool largeFont)
43{{
44    if (largeFont && fontSize > 0)
45    {{
46        return fontSize - 1;
47    }}
48    return fontSize;
49}}
50}}  // anonymous namespace
51
52void Overlay::initOverlayWidgets()
53{{
54    const bool kLargeFont = rx::IsAndroid();
55
56    {init_widgets}
57}}
58
59}}  // namespace gl
60
61"""
62
63OUT_HEADER_FILE_TEMPLATE = u"""// GENERATED FILE - DO NOT EDIT.
64// Generated by {script_name} using data from {input_file_name}.
65//
66// Copyright 2019 The ANGLE Project Authors. All rights reserved.
67// Use of this source code is governed by a BSD-style license that can be
68// found in the LICENSE file.
69//
70// {out_file_name}:
71//   Autogenerated overlay widget declarations.
72
73namespace gl
74{{
75enum class WidgetId
76{{
77{widget_ids}
78    InvalidEnum,
79    EnumCount = InvalidEnum,
80}};
81
82// We can use this "X" macro to generate multiple code patterns.
83#define ANGLE_WIDGET_ID_X(PROC) \
84{widget_x_defs}
85
86}}  // namespace gl
87"""
88
89WIDGET_INIT_TEMPLATE = u"""{{
90const int32_t fontSize = GetFontSize({font_size}, kLargeFont);
91const int32_t offsetX = {offset_x};
92const int32_t offsetY = {offset_y};
93const int32_t width = {width};
94const int32_t height = {height};
95
96widget->{subwidget}type          = WidgetType::{type};
97widget->{subwidget}fontSize      = fontSize;
98widget->{subwidget}coords[0]     = {coord0};
99widget->{subwidget}coords[1]     = {coord1};
100widget->{subwidget}coords[2]     = {coord2};
101widget->{subwidget}coords[3]     = {coord3};
102widget->{subwidget}color[0]      = {color_r}f;
103widget->{subwidget}color[1]      = {color_g}f;
104widget->{subwidget}color[2]      = {color_b}f;
105widget->{subwidget}color[3]      = {color_a}f;
106widget->{subwidget}matchToWidget = {match_to};
107}}
108"""
109
110WIDGET_ID_TEMPLATE = """    // {comment}
111    {name},
112"""
113
114
115def extract_type_and_constructor(properties):
116    constructor = properties['type']
117    args_separated = constructor.split('(', 1)
118    if len(args_separated) == 1:
119        return constructor, constructor
120
121    type_no_constructor = args_separated[0]
122    return type_no_constructor, constructor
123
124
125def get_font_size_constant(properties):
126    return 'kFontMip' + properties['font'].capitalize()
127
128
129def is_graph_type(type):
130    return type == 'RunningGraph' or type == 'RunningHistogram'
131
132
133def is_text_type(type):
134    return not is_graph_type(type)
135
136
137class OverlayWidget:
138
139    def __init__(self, properties, is_graph_description=False):
140        if not is_graph_description:
141            self.name = properties['name']
142        self.type, self.constructor = extract_type_and_constructor(properties)
143        self.extract_common(properties)
144
145        if is_graph_type(self.type):
146            description_properties = properties['description']
147            description_properties['type'] = 'Text'
148            self.description = OverlayWidget(description_properties, True)
149
150    def extract_common(self, properties):
151        self.color = properties['color']
152        self.coords = properties['coords']
153        self.match_to = properties.get('match_to', None)
154        if is_graph_type(self.type):
155            self.bar_width = properties['bar_width']
156            self.height = properties['height']
157        else:
158            self.font = get_font_size_constant(properties)
159            self.length = properties['length']
160
161        self.negative_alignment = [False, False]
162
163
164def get_relative_coord_widget(coord, widgets_so_far):
165    if not isinstance(coord, str):
166        return None
167
168    coord_split = coord.split('.')
169    widget = widgets_so_far[coord_split[0]]
170    if len(coord_split) > 3:
171        widget = widget.description
172
173    return widget
174
175
176def parse_relative_coord(coord):
177
178    # The input coordinate (widget.coord[axis]) is either:
179    #
180    # - a number
181    # - other_widget.edge.mode:
182    #   * edge is in {left, right} or {top, bottom} based on axis
183    #   * mode is in {align, adjacent}
184    # - other_widget.desc.edge.mode: this is similar to other_widget.edge.mode, except
185    #                                it refers to a graph widget's description widget
186    #
187    # This function parses the last two possibilities.
188    if not isinstance(coord, str):
189        return None, None, None
190
191    coord_split = coord.split('.')
192    coords_expr = 'mState.mOverlayWidgets[WidgetId::' + coord_split[0] + ']'
193    if len(coord_split) > 3:
194        assert len(coord_split) == 4 and coord_split[1] == 'desc'
195        coords_expr += '->getDescriptionWidget()'
196    coords_expr += '->coords'
197
198    edge = coord_split[-2]
199    mode = coord_split[-1]
200
201    return coords_expr, edge, mode
202
203
204def is_negative_coord(coords, axis, widgets_so_far):
205
206    other_widget = get_relative_coord_widget(coords[axis], widgets_so_far)
207    if other_widget is not None:
208        # We simply need to know if other_widget's coordinate is negative or not.
209        return other_widget.negative_alignment[axis]
210
211    return coords[axis] < 0
212
213
214def set_alignment_flags(overlay_widget, widgets_so_far):
215    overlay_widget.negative_alignment[0] = is_negative_coord(overlay_widget.coords, 0,
216                                                             widgets_so_far)
217    overlay_widget.negative_alignment[1] = is_negative_coord(overlay_widget.coords, 1,
218                                                             widgets_so_far)
219
220    if is_graph_type(overlay_widget.type):
221        set_alignment_flags(overlay_widget.description, widgets_so_far)
222
223
224def get_offset_helper(widget, axis, smaller_coord_side):
225    # Assume axis is X.  This function returns two values:
226    # - An offset where the bounding box is placed at,
227    # - Whether this offset is for the left or right edge.
228    #
229    # The input coordinate (widget.coord[axis]) is either:
230    #
231    # - a number: in this case, the offset is that number, and its sign
232    #             determines whether this refers to the left or right edge of
233    #             the bounding box.
234    # - other_widget[.desc].edge.mode: this has multiple possibilities:
235    #   * edge=left, mode=align: the offset is other_widget[.desc].left, the edge is left.
236    #   * edge=left, mode=adjacent: the offset is other_widget[.desc].left, the edge is right.
237    #   * edge=right, mode=align: the offset is other_widget[.desc].right, the edge is right.
238    #   * edge=right, mode=adjacent: the offset is other_widget[.desc].right, the edge is left.
239    #
240    # The case for the Y axis is similar, with the edge values being top or bottom.
241
242    coord = widget.coords[axis]
243
244    other_coords_expr, relative_edge, relative_mode = parse_relative_coord(coord)
245    if other_coords_expr is None:
246        is_left = coord >= 0
247        return coord, is_left
248
249    is_left = relative_edge == smaller_coord_side
250    is_align = relative_mode == 'align'
251
252    other_widget_coord_index = axis + (0 if is_left else 2)
253    offset = other_coords_expr + '[' + str(other_widget_coord_index) + ']'
254
255    return offset, is_left == is_align
256
257
258def get_offset_x(widget):
259    return get_offset_helper(widget, 0, 'left')
260
261
262def get_offset_y(widget):
263    return get_offset_helper(widget, 1, 'top')
264
265
266def get_bounding_box_coords(offset, width, offset_is_left, is_left_aligned):
267    # See comment in generate_widget_init_helper.  This function is implementing the following:
268    #
269    # -  offset_is_left &&  is_left_aligned: [offset, offset + width]
270    # -  offset_is_left && !is_left_aligned: [offset, std::min(offset + width, -1)]
271    # - !offset_is_left &&  is_left_aligned: [std::max(1, offset - width), offset]
272    # - !offset_is_left && !is_left_aligned: [offset - width, offset]
273
274    coord_left = offset if offset_is_left else (offset + ' - ' + width)
275    coord_right = (offset + ' + ' + width) if offset_is_left else offset
276
277    if offset_is_left and not is_left_aligned:
278        coord_right = 'std::min(' + coord_right + ', -1)'
279    if not offset_is_left and is_left_aligned:
280        coord_left = 'std::max(' + coord_left + ', 1)'
281
282    return coord_left, coord_right
283
284
285def generate_widget_init_helper(widget, is_graph_description=False):
286    font_size = '0'
287
288    # Common attributes
289    color = [channel / 255.0 for channel in widget.color]
290    offset_x, offset_x_is_left = get_offset_x(widget)
291    offset_y, offset_y_is_top = get_offset_y(widget)
292
293    if is_text_type(widget.type):
294        # Attributes deriven from text properties
295        font_size = widget.font
296        width = str(widget.length) + ' * (kFontGlyphWidth >> fontSize)'
297        height = '(kFontGlyphHeight >> fontSize)'
298    else:
299        # Attributes deriven from graph properties
300        width = str(widget.bar_width) + ' * static_cast<uint32_t>(widget->runningValues.size())'
301        height = widget.height
302
303    is_left_aligned = not widget.negative_alignment[0]
304    is_top_aligned = not widget.negative_alignment[1]
305
306    # We have offset_x, offset_y, width and height which together determine the bounding box.  If
307    # offset_x_is_left, the bounding box X would be in [offset_x, offset_x + width], otherwise it
308    # would be in [offset_x - width, offset_x].  Similarly for y.  Since we use negative values to
309    # mean aligned to the right side of the screen, we need to make sure that:
310    #
311    # - if left aligned: offset_x - width is at minimum 1
312    # - if right aligned: offset_x + width is at maximum -1
313    #
314    # We therefore have the following combinations for the X axis:
315    #
316    # -  offset_x_is_left &&  is_left_aligned: [offset_x, offset_x + width]
317    # -  offset_x_is_left && !is_left_aligned: [offset_x, std::min(offset_x + width, -1)]
318    # - !offset_x_is_left &&  is_left_aligned: [std::max(1, offset_x - width), offset_x]
319    # - !offset_x_is_left && !is_left_aligned: [offset_x - width, offset_x]
320    #
321    # Similarly for y.
322    coord0, coord2 = get_bounding_box_coords('offsetX', 'width', offset_x_is_left, is_left_aligned)
323    coord1, coord3 = get_bounding_box_coords('offsetY', 'height', offset_y_is_top, is_top_aligned)
324
325    match_to = 'nullptr' if widget.match_to is None else \
326               'mState.mOverlayWidgets[WidgetId::' + widget.match_to + '].get()'
327
328    return WIDGET_INIT_TEMPLATE.format(
329        subwidget='description.' if is_graph_description else '',
330        offset_x=offset_x,
331        offset_y=offset_y,
332        width=width,
333        height=height,
334        type=widget.type,
335        font_size=font_size,
336        coord0=coord0,
337        coord1=coord1,
338        coord2=coord2,
339        coord3=coord3,
340        color_r=color[0],
341        color_g=color[1],
342        color_b=color[2],
343        color_a=color[3],
344        match_to=match_to)
345
346
347def generate_widget_init(widget):
348    widget_init = '{\n' + widget.type + ' *widget = new ' + widget.constructor + ';\n'
349
350    widget_init += generate_widget_init_helper(widget)
351    widget_init += 'mState.mOverlayWidgets[WidgetId::' + widget.name + '].reset(widget);\n'
352
353    if is_graph_type(widget.type):
354        widget_init += generate_widget_init_helper(widget.description, True)
355
356    widget_init += '}\n'
357
358    return widget_init
359
360
361def main():
362    if len(sys.argv) == 2 and sys.argv[1] == 'inputs':
363        print(IN_JSON_FILE_NAME)
364        return
365    if len(sys.argv) == 2 and sys.argv[1] == 'outputs':
366        outputs = [
367            OUT_SOURCE_FILE_NAME,
368            OUT_HEADER_FILE_NAME,
369        ]
370        print(','.join(outputs))
371        return
372
373    with open(IN_JSON_FILE_NAME) as fin:
374        layout = json.loads(fin.read())
375
376    widgets = layout['widgets']
377
378    # Read the layouts from the json file and determine alignment of widgets (as they can refer to
379    # other widgets.
380    overlay_widgets = {}
381    for widget_properties in widgets:
382        widget = OverlayWidget(widget_properties)
383        overlay_widgets[widget.name] = widget
384        set_alignment_flags(widget, overlay_widgets)
385
386    # Go over the widgets again and generate initialization code.  Note that we need to iterate over
387    # the widgets in order, so we can't use the overlay_widgets dictionary for iteration.
388    init_widgets = []
389    for widget_properties in widgets:
390        init_widgets.append(generate_widget_init(overlay_widgets[widget_properties['name']]))
391
392    with open(OUT_SOURCE_FILE_NAME, 'w') as outfile:
393        outfile.write(
394            OUT_SOURCE_FILE_TEMPLATE.format(
395                script_name=os.path.basename(__file__),
396                input_file_name=IN_JSON_FILE_NAME,
397                out_file_name=OUT_SOURCE_FILE_NAME,
398                init_widgets='\n'.join(init_widgets)))
399        outfile.close()
400
401    with open(OUT_HEADER_FILE_NAME, 'w') as outfile:
402        widget_ids = [WIDGET_ID_TEMPLATE.format(**widget) for widget in widgets]
403        widget_x_defs = ["PROC(" + widget['name'] + ")" for widget in widgets]
404
405        outfile.write(
406            OUT_HEADER_FILE_TEMPLATE.format(
407                script_name=os.path.basename(__file__),
408                input_file_name=IN_JSON_FILE_NAME,
409                out_file_name=OUT_SOURCE_FILE_NAME,
410                widget_ids=''.join(widget_ids),
411                widget_x_defs=' \\\n'.join(widget_x_defs)))
412        outfile.close()
413
414
415if __name__ == '__main__':
416    sys.exit(main())
417