#! /usr/bin/python # Copyright 2019 The ANGLE Project Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # # gen_overlay_widgets.py: # Code generation for overlay widgets. Should be run when the widgets declaration file, # overlay_widgets.json, is changed. # NOTE: don't run this script directly. Run scripts/run_code_generation.py. import json import sys OUT_SOURCE_FILE_NAME = 'Overlay_autogen.cpp' OUT_HEADER_FILE_NAME = 'Overlay_autogen.h' IN_JSON_FILE_NAME = 'overlay_widgets.json' OUT_SOURCE_FILE_TEMPLATE = u"""// GENERATED FILE - DO NOT EDIT. // Generated by {script_name} using data from {input_file_name}. // // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // {out_file_name}: // Autogenerated overlay widget declarations. #include "libANGLE/renderer/driver_utils.h" #include "libANGLE/Overlay.h" #include "libANGLE/OverlayWidgets.h" #include "libANGLE/Overlay_font_autogen.h" namespace gl {{ using namespace overlay; namespace {{ int GetFontSize(int fontSize, bool largeFont) {{ if (largeFont && fontSize > 0) {{ return fontSize - 1; }} return fontSize; }} }} // anonymous namespace void Overlay::initOverlayWidgets() {{ const bool kLargeFont = rx::IsAndroid(); {init_widgets} }} }} // namespace gl """ OUT_HEADER_FILE_TEMPLATE = u"""// GENERATED FILE - DO NOT EDIT. // Generated by {script_name} using data from {input_file_name}. // // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // {out_file_name}: // Autogenerated overlay widget declarations. namespace gl {{ enum class WidgetId {{ {widget_ids} InvalidEnum, EnumCount = InvalidEnum, }}; // We can use this "X" macro to generate multiple code patterns. #define ANGLE_WIDGET_ID_X(PROC) \ {widget_x_defs} }} // namespace gl """ WIDGET_INIT_TEMPLATE = u"""{{ const int32_t fontSize = GetFontSize({font_size}, kLargeFont); const int32_t offsetX = {offset_x}; const int32_t offsetY = {offset_y}; const int32_t width = {width}; const int32_t height = {height}; widget->{subwidget}type = WidgetType::{type}; widget->{subwidget}fontSize = fontSize; widget->{subwidget}coords[0] = {coord0}; widget->{subwidget}coords[1] = {coord1}; widget->{subwidget}coords[2] = {coord2}; widget->{subwidget}coords[3] = {coord3}; widget->{subwidget}color[0] = {color_r}f; widget->{subwidget}color[1] = {color_g}f; widget->{subwidget}color[2] = {color_b}f; widget->{subwidget}color[3] = {color_a}f; }} """ WIDGET_ID_TEMPLATE = """ // {comment} {name}, """ def extract_type_and_constructor(properties): constructor = properties['type'] args_separated = constructor.split('(', 1) if len(args_separated) == 1: return constructor, constructor type_no_constructor = args_separated[0] return type_no_constructor, constructor def get_font_size_constant(properties): return 'kFontLayer' + properties['font'].capitalize() def is_graph_type(type): return type == 'RunningGraph' or type == 'RunningHistogram' def is_text_type(type): return not is_graph_type(type) class OverlayWidget: def __init__(self, properties, is_graph_description=False): if not is_graph_description: self.name = properties['name'] self.type, self.constructor = extract_type_and_constructor(properties) self.extract_common(properties) if is_graph_type(self.type): description_properties = properties['description'] description_properties['type'] = 'Text' self.description = OverlayWidget(description_properties, True) def extract_common(self, properties): self.color = properties['color'] self.coords = properties['coords'] if is_graph_type(self.type): self.bar_width = properties['bar_width'] self.height = properties['height'] else: self.font = get_font_size_constant(properties) self.length = properties['length'] self.negative_alignment = [False, False] def is_negative_coord(coords, axis, widgets_so_far): if isinstance(coords[axis], unicode): coord_split = coords[axis].split('.') # The coordinate is in the form other_widget.edge.mode # We simply need to know if other_widget's coordinate is negative or not. return widgets_so_far[coord_split[0]].negative_alignment[axis] return coords[axis] < 0 def set_alignment_flags(overlay_widget, widgets_so_far): overlay_widget.negative_alignment[0] = is_negative_coord(overlay_widget.coords, 0, widgets_so_far) overlay_widget.negative_alignment[1] = is_negative_coord(overlay_widget.coords, 1, widgets_so_far) if is_graph_type(overlay_widget.type): set_alignment_flags(overlay_widget.description, widgets_so_far) def get_offset_helper(widget, axis, smaller_coord_side): # Assume axis is X. This function returns two values: # - An offset where the bounding box is placed at, # - Whether this offset is for the left or right edge. # # The input coordinate (widget.coord[axis]) is either: # # - 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. # - other_widget.edge.mode: this has multiple possibilities: # * edge=left, mode=align: the offset is other_widget.left, the edge is left. # * edge=left, mode=adjacent: the offset is other_widget.left, the edge is right. # * edge=right, mode=align: the offset is other_widget.right, the edge is right. # * edge=right, mode=adjacent: the offset is other_widget.right, the edge is left. # # The case for the Y axis is similar, with the edge values being top or bottom. coord = widget.coords[axis] if not isinstance(coord, unicode): is_left = coord >= 0 return coord, is_left coord_split = coord.split('.') is_left = coord_split[1] == smaller_coord_side is_align = coord_split[2] == 'align' other_widget_coords = 'mState.mOverlayWidgets[WidgetId::' + coord_split[0] + ']->coords' other_widget_coord_index = axis + (0 if is_left else 2) offset = other_widget_coords + '[' + str(other_widget_coord_index) + ']' return offset, is_left == is_align def get_offset_x(widget): return get_offset_helper(widget, 0, 'left') def get_offset_y(widget): return get_offset_helper(widget, 1, 'top') def get_bounding_box_coords(offset, width, offset_is_left, is_left_aligned): # See comment in generate_widget_init_helper. This function is implementing the following: # # - offset_is_left && is_left_aligned: [offset, offset + width] # - offset_is_left && !is_left_aligned: [offset, std::min(offset + width, -1)] # - !offset_is_left && is_left_aligned: [std::max(1, offset - width), offset] # - !offset_is_left && !is_left_aligned: [offset - width, offset] coord_left = offset if offset_is_left else (offset + ' - ' + width) coord_right = (offset + ' + ' + width) if offset_is_left else offset if offset_is_left and not is_left_aligned: coord_right = 'std::min(' + coord_right + ', -1)' if not offset_is_left and is_left_aligned: coord_left = 'std::max(' + coord_left + ', 1)' return coord_left, coord_right def generate_widget_init_helper(widget, is_graph_description=False): font_size = '0' # Common attributes color = [channel / 255.0 for channel in widget.color] offset_x, offset_x_is_left = get_offset_x(widget) offset_y, offset_y_is_top = get_offset_y(widget) if is_text_type(widget.type): # Attributes deriven from text properties font_size = widget.font width = str(widget.length) + ' * kFontGlyphWidths[fontSize]' height = 'kFontGlyphHeights[fontSize]' else: # Attributes deriven from graph properties width = str(widget.bar_width) + ' * static_cast(widget->runningValues.size())' height = widget.height is_left_aligned = not widget.negative_alignment[0] is_top_aligned = not widget.negative_alignment[1] # We have offset_x, offset_y, width and height which together determine the bounding box. If # offset_x_is_left, the bounding box X would be in [offset_x, offset_x + width], otherwise it # would be in [offset_x - width, offset_x]. Similarly for y. Since we use negative values to # mean aligned to the right side of the screen, we need to make sure that: # # - if left aligned: offset_x - width is at minimum 1 # - if right aligned: offset_x + width is at maximum -1 # # We therefore have the following combinations for the X axis: # # - offset_x_is_left && is_left_aligned: [offset_x, offset_x + width] # - offset_x_is_left && !is_left_aligned: [offset_x, std::min(offset_x + width, -1)] # - !offset_x_is_left && is_left_aligned: [std::max(1, offset_x - width), offset_x] # - !offset_x_is_left && !is_left_aligned: [offset_x - width, offset_x] # # Similarly for y. coord0, coord2 = get_bounding_box_coords('offsetX', 'width', offset_x_is_left, is_left_aligned) coord1, coord3 = get_bounding_box_coords('offsetY', 'height', offset_y_is_top, is_top_aligned) return WIDGET_INIT_TEMPLATE.format( subwidget='description.' if is_graph_description else '', offset_x=offset_x, offset_y=offset_y, width=width, height=height, type=widget.type, font_size=font_size, coord0=coord0, coord1=coord1, coord2=coord2, coord3=coord3, color_r=color[0], color_g=color[1], color_b=color[2], color_a=color[3]) def generate_widget_init(widget): widget_init = '{\n' + widget.type + ' *widget = new ' + widget.constructor + ';\n' widget_init += generate_widget_init_helper(widget) widget_init += 'mState.mOverlayWidgets[WidgetId::' + widget.name + '].reset(widget);\n' if is_graph_type(widget.type): widget_init += generate_widget_init_helper(widget.description, True) widget_init += '}\n' return widget_init def main(): if len(sys.argv) == 2 and sys.argv[1] == 'inputs': print(IN_JSON_FILE_NAME) return if len(sys.argv) == 2 and sys.argv[1] == 'outputs': outputs = [ OUT_SOURCE_FILE_NAME, OUT_HEADER_FILE_NAME, ] print(','.join(outputs)) return with open(IN_JSON_FILE_NAME) as fin: layout = json.loads(fin.read()) widgets = layout['widgets'] # Read the layouts from the json file and determine alignment of widgets (as they can refer to # other widgets. overlay_widgets = {} for widget_properties in widgets: widget = OverlayWidget(widget_properties) overlay_widgets[widget.name] = widget set_alignment_flags(widget, overlay_widgets) # Go over the widgets again and generate initialization code. Note that we need to iterate over # the widgets in order, so we can't use the overlay_widgets dictionary for iteration. init_widgets = [] for widget_properties in widgets: init_widgets.append(generate_widget_init(overlay_widgets[widget_properties['name']])) with open(OUT_SOURCE_FILE_NAME, 'w') as outfile: outfile.write( OUT_SOURCE_FILE_TEMPLATE.format( script_name=__file__, input_file_name=IN_JSON_FILE_NAME, out_file_name=OUT_SOURCE_FILE_NAME, init_widgets='\n'.join(init_widgets))) outfile.close() with open(OUT_HEADER_FILE_NAME, 'w') as outfile: widget_ids = [WIDGET_ID_TEMPLATE.format(**widget) for widget in widgets] widget_x_defs = ["PROC(" + widget['name'] + ")" for widget in widgets] outfile.write( OUT_HEADER_FILE_TEMPLATE.format( script_name=__file__, input_file_name=IN_JSON_FILE_NAME, out_file_name=OUT_SOURCE_FILE_NAME, widget_ids=''.join(widget_ids), widget_x_defs=' \\\n'.join(widget_x_defs))) outfile.close() if __name__ == '__main__': sys.exit(main())