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