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