• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
106}}
107"""
108
109WIDGET_ID_TEMPLATE = """    // {comment}
110    {name},
111"""
112
113
114def extract_type_and_constructor(properties):
115    constructor = properties['type']
116    args_separated = constructor.split('(', 1)
117    if len(args_separated) == 1:
118        return constructor, constructor
119
120    type_no_constructor = args_separated[0]
121    return type_no_constructor, constructor
122
123
124def get_font_size_constant(properties):
125    return 'kFontMip' + properties['font'].capitalize()
126
127
128def is_graph_type(type):
129    return type == 'RunningGraph' or type == 'RunningHistogram'
130
131
132def is_text_type(type):
133    return not is_graph_type(type)
134
135
136class OverlayWidget:
137
138    def __init__(self, properties, is_graph_description=False):
139        if not is_graph_description:
140            self.name = properties['name']
141        self.type, self.constructor = extract_type_and_constructor(properties)
142        self.extract_common(properties)
143
144        if is_graph_type(self.type):
145            description_properties = properties['description']
146            description_properties['type'] = 'Text'
147            self.description = OverlayWidget(description_properties, True)
148
149    def extract_common(self, properties):
150        self.color = properties['color']
151        self.coords = properties['coords']
152        if is_graph_type(self.type):
153            self.bar_width = properties['bar_width']
154            self.height = properties['height']
155        else:
156            self.font = get_font_size_constant(properties)
157            self.length = properties['length']
158
159        self.negative_alignment = [False, False]
160
161
162def is_negative_coord(coords, axis, widgets_so_far):
163
164    if isinstance(coords[axis], str):
165        coord_split = coords[axis].split('.')
166        # The coordinate is in the form other_widget.edge.mode
167        # We simply need to know if other_widget's coordinate is negative or not.
168        return widgets_so_far[coord_split[0]].negative_alignment[axis]
169
170    return coords[axis] < 0
171
172
173def set_alignment_flags(overlay_widget, widgets_so_far):
174    overlay_widget.negative_alignment[0] = is_negative_coord(overlay_widget.coords, 0,
175                                                             widgets_so_far)
176    overlay_widget.negative_alignment[1] = is_negative_coord(overlay_widget.coords, 1,
177                                                             widgets_so_far)
178
179    if is_graph_type(overlay_widget.type):
180        set_alignment_flags(overlay_widget.description, widgets_so_far)
181
182
183def get_offset_helper(widget, axis, smaller_coord_side):
184    # Assume axis is X.  This function returns two values:
185    # - An offset where the bounding box is placed at,
186    # - Whether this offset is for the left or right edge.
187    #
188    # The input coordinate (widget.coord[axis]) is either:
189    #
190    # - a number: in this case, the offset is that number, and its sign determines whether this refers to the left or right edge of the bounding box.
191    # - other_widget.edge.mode: this has multiple possibilities:
192    #   * edge=left, mode=align: the offset is other_widget.left, the edge is left.
193    #   * edge=left, mode=adjacent: the offset is other_widget.left, the edge is right.
194    #   * edge=right, mode=align: the offset is other_widget.right, the edge is right.
195    #   * edge=right, mode=adjacent: the offset is other_widget.right, the edge is left.
196    #
197    # The case for the Y axis is similar, with the edge values being top or bottom.
198
199    coord = widget.coords[axis]
200    if not isinstance(coord, str):
201        is_left = coord >= 0
202        return coord, is_left
203
204    coord_split = coord.split('.')
205
206    is_left = coord_split[1] == smaller_coord_side
207    is_align = coord_split[2] == 'align'
208
209    other_widget_coords = 'mState.mOverlayWidgets[WidgetId::' + coord_split[0] + ']->coords'
210    other_widget_coord_index = axis + (0 if is_left else 2)
211    offset = other_widget_coords + '[' + str(other_widget_coord_index) + ']'
212
213    return offset, is_left == is_align
214
215
216def get_offset_x(widget):
217    return get_offset_helper(widget, 0, 'left')
218
219
220def get_offset_y(widget):
221    return get_offset_helper(widget, 1, 'top')
222
223
224def get_bounding_box_coords(offset, width, offset_is_left, is_left_aligned):
225    # See comment in generate_widget_init_helper.  This function is implementing the following:
226    #
227    # -  offset_is_left &&  is_left_aligned: [offset, offset + width]
228    # -  offset_is_left && !is_left_aligned: [offset, std::min(offset + width, -1)]
229    # - !offset_is_left &&  is_left_aligned: [std::max(1, offset - width), offset]
230    # - !offset_is_left && !is_left_aligned: [offset - width, offset]
231
232    coord_left = offset if offset_is_left else (offset + ' - ' + width)
233    coord_right = (offset + ' + ' + width) if offset_is_left else offset
234
235    if offset_is_left and not is_left_aligned:
236        coord_right = 'std::min(' + coord_right + ', -1)'
237    if not offset_is_left and is_left_aligned:
238        coord_left = 'std::max(' + coord_left + ', 1)'
239
240    return coord_left, coord_right
241
242
243def generate_widget_init_helper(widget, is_graph_description=False):
244    font_size = '0'
245
246    # Common attributes
247    color = [channel / 255.0 for channel in widget.color]
248    offset_x, offset_x_is_left = get_offset_x(widget)
249    offset_y, offset_y_is_top = get_offset_y(widget)
250
251    if is_text_type(widget.type):
252        # Attributes deriven from text properties
253        font_size = widget.font
254        width = str(widget.length) + ' * (kFontGlyphWidth >> fontSize)'
255        height = '(kFontGlyphHeight >> fontSize)'
256    else:
257        # Attributes deriven from graph properties
258        width = str(widget.bar_width) + ' * static_cast<uint32_t>(widget->runningValues.size())'
259        height = widget.height
260
261    is_left_aligned = not widget.negative_alignment[0]
262    is_top_aligned = not widget.negative_alignment[1]
263
264    # We have offset_x, offset_y, width and height which together determine the bounding box.  If
265    # offset_x_is_left, the bounding box X would be in [offset_x, offset_x + width], otherwise it
266    # would be in [offset_x - width, offset_x].  Similarly for y.  Since we use negative values to
267    # mean aligned to the right side of the screen, we need to make sure that:
268    #
269    # - if left aligned: offset_x - width is at minimum 1
270    # - if right aligned: offset_x + width is at maximum -1
271    #
272    # We therefore have the following combinations for the X axis:
273    #
274    # -  offset_x_is_left &&  is_left_aligned: [offset_x, offset_x + width]
275    # -  offset_x_is_left && !is_left_aligned: [offset_x, std::min(offset_x + width, -1)]
276    # - !offset_x_is_left &&  is_left_aligned: [std::max(1, offset_x - width), offset_x]
277    # - !offset_x_is_left && !is_left_aligned: [offset_x - width, offset_x]
278    #
279    # Similarly for y.
280    coord0, coord2 = get_bounding_box_coords('offsetX', 'width', offset_x_is_left, is_left_aligned)
281    coord1, coord3 = get_bounding_box_coords('offsetY', 'height', offset_y_is_top, is_top_aligned)
282
283    return WIDGET_INIT_TEMPLATE.format(
284        subwidget='description.' if is_graph_description else '',
285        offset_x=offset_x,
286        offset_y=offset_y,
287        width=width,
288        height=height,
289        type=widget.type,
290        font_size=font_size,
291        coord0=coord0,
292        coord1=coord1,
293        coord2=coord2,
294        coord3=coord3,
295        color_r=color[0],
296        color_g=color[1],
297        color_b=color[2],
298        color_a=color[3])
299
300
301def generate_widget_init(widget):
302    widget_init = '{\n' + widget.type + ' *widget = new ' + widget.constructor + ';\n'
303
304    widget_init += generate_widget_init_helper(widget)
305    widget_init += 'mState.mOverlayWidgets[WidgetId::' + widget.name + '].reset(widget);\n'
306
307    if is_graph_type(widget.type):
308        widget_init += generate_widget_init_helper(widget.description, True)
309
310    widget_init += '}\n'
311
312    return widget_init
313
314
315def main():
316    if len(sys.argv) == 2 and sys.argv[1] == 'inputs':
317        print(IN_JSON_FILE_NAME)
318        return
319    if len(sys.argv) == 2 and sys.argv[1] == 'outputs':
320        outputs = [
321            OUT_SOURCE_FILE_NAME,
322            OUT_HEADER_FILE_NAME,
323        ]
324        print(','.join(outputs))
325        return
326
327    with open(IN_JSON_FILE_NAME) as fin:
328        layout = json.loads(fin.read())
329
330    widgets = layout['widgets']
331
332    # Read the layouts from the json file and determine alignment of widgets (as they can refer to
333    # other widgets.
334    overlay_widgets = {}
335    for widget_properties in widgets:
336        widget = OverlayWidget(widget_properties)
337        overlay_widgets[widget.name] = widget
338        set_alignment_flags(widget, overlay_widgets)
339
340    # Go over the widgets again and generate initialization code.  Note that we need to iterate over
341    # the widgets in order, so we can't use the overlay_widgets dictionary for iteration.
342    init_widgets = []
343    for widget_properties in widgets:
344        init_widgets.append(generate_widget_init(overlay_widgets[widget_properties['name']]))
345
346    with open(OUT_SOURCE_FILE_NAME, 'w') as outfile:
347        outfile.write(
348            OUT_SOURCE_FILE_TEMPLATE.format(
349                script_name=os.path.basename(__file__),
350                input_file_name=IN_JSON_FILE_NAME,
351                out_file_name=OUT_SOURCE_FILE_NAME,
352                init_widgets='\n'.join(init_widgets)))
353        outfile.close()
354
355    with open(OUT_HEADER_FILE_NAME, 'w') as outfile:
356        widget_ids = [WIDGET_ID_TEMPLATE.format(**widget) for widget in widgets]
357        widget_x_defs = ["PROC(" + widget['name'] + ")" for widget in widgets]
358
359        outfile.write(
360            OUT_HEADER_FILE_TEMPLATE.format(
361                script_name=os.path.basename(__file__),
362                input_file_name=IN_JSON_FILE_NAME,
363                out_file_name=OUT_SOURCE_FILE_NAME,
364                widget_ids=''.join(widget_ids),
365                widget_x_defs=' \\\n'.join(widget_x_defs)))
366        outfile.close()
367
368
369if __name__ == '__main__':
370    sys.exit(main())
371