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