[
  {
    "path": ".gitignore",
    "content": "/target\n/build\n.idea"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"piet-metal\"\nversion = \"0.0.0\"\nauthors = [\"Raph Levien <raph.levien@gmail.com>\"]\ndescription = \"A Metal-based renderer for the piet 2D graphics abstraction.\"\nlicense = \"MIT/Apache-2.0\"\nedition = \"2018\"\nkeywords = [\"graphics\", \"2d\"]\ncategories = [\"rendering::graphics-api\"]\n\n[dependencies]\nkurbo = \"0.5.6\"\npiet-gpu-derive = { path = \"./piet-gpu-derive\" }\n\n# this is for reading the tiger, will be factored out\nroxmltree = \"0.6.0\"\n\n[lib]\nname = \"piet_metal\"\ncrate-type = [\"staticlib\"]\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "Copyright (c) 2019 Raph Levien\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# piet-metal\n\nThis repository is currently an experiment in using GPU compute to implement the piet 2D graphics API. In its initial stages it is an Objective-C macOS application, to make it easier to use Xcode tools. When it becomes more functional, Rust bindings will be added, with the Objective-C code for the Metal bindings built from the Rust library's build.rs.\n\n## Following along\n\nI'm discussing the design and my findings in the [#druid] stream on xi Zulip. I've also creates a [notes document].\n\n## License\n\nLicensed under either of these:\n\n * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or\n   https://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or\n   https://opensource.org/licenses/MIT)\n\n[notes document]: https://docs.google.com/document/d/1LILagXyJgYtlm6y83x1Mc2VoNfOcvW_ZiCldZbs4yO8/edit?usp=sharing\n[#druid]: https://xi.zulipchat.com/#narrow/stream/147926-druid\n"
  },
  {
    "path": "TestApp/AppDelegate.h",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n#import <Cocoa/Cocoa.h>\n\n@interface AppDelegate : NSObject <NSApplicationDelegate>\n\n\n@end\n\n"
  },
  {
    "path": "TestApp/AppDelegate.m",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n#import \"AppDelegate.h\"\n\n@interface AppDelegate ()\n\n@end\n\n@implementation AppDelegate\n\n- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {\n    // Insert code here to initialize your application\n}\n\n\n- (void)applicationWillTerminate:(NSNotification *)aNotification {\n    // Insert code here to tear down your application\n}\n\n\n@end\n"
  },
  {
    "path": "TestApp/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"16x16\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"16x16\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"32x32\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"32x32\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"128x128\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"128x128\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"256x256\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"256x256\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"512x512\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"512x512\",\n      \"scale\" : \"2x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "TestApp/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "TestApp/Base.lproj/Main.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"14490.70\" targetRuntime=\"MacOSX.Cocoa\" propertyAccessControl=\"none\" useAutolayout=\"YES\" initialViewController=\"B8D-0N-5wS\">\n    <dependencies>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.CocoaPlugin\" version=\"14490.70\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--Application-->\n        <scene sceneID=\"JPo-4y-FX3\">\n            <objects>\n                <application id=\"hnw-xV-0zn\" sceneMemberID=\"viewController\">\n                    <menu key=\"mainMenu\" title=\"Main Menu\" systemMenu=\"main\" id=\"AYu-sK-qS6\">\n                        <items>\n                            <menuItem title=\"piet-metal\" id=\"1Xt-HY-uBw\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"piet-metal\" systemMenu=\"apple\" id=\"uQy-DD-JDr\">\n                                    <items>\n                                        <menuItem title=\"About piet-metal\" id=\"5kV-Vb-QxS\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"orderFrontStandardAboutPanel:\" target=\"Ady-hI-5gd\" id=\"Exp-CZ-Vem\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"VOq-y0-SEH\"/>\n                                        <menuItem title=\"Preferences…\" keyEquivalent=\",\" id=\"BOF-NM-1cW\"/>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"wFC-TO-SCJ\"/>\n                                        <menuItem title=\"Services\" id=\"NMo-om-nkz\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Services\" systemMenu=\"services\" id=\"hz9-B4-Xy5\"/>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"4je-JR-u6R\"/>\n                                        <menuItem title=\"Hide piet-metal\" keyEquivalent=\"h\" id=\"Olw-nP-bQN\">\n                                            <connections>\n                                                <action selector=\"hide:\" target=\"Ady-hI-5gd\" id=\"PnN-Uc-m68\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Hide Others\" keyEquivalent=\"h\" id=\"Vdr-fp-XzO\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"hideOtherApplications:\" target=\"Ady-hI-5gd\" id=\"VT4-aY-XCT\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Show All\" id=\"Kd2-mp-pUS\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"unhideAllApplications:\" target=\"Ady-hI-5gd\" id=\"Dhg-Le-xox\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"kCx-OE-vgT\"/>\n                                        <menuItem title=\"Quit piet-metal\" keyEquivalent=\"q\" id=\"4sb-4s-VLi\">\n                                            <connections>\n                                                <action selector=\"terminate:\" target=\"Ady-hI-5gd\" id=\"Te7-pn-YzF\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"File\" id=\"dMs-cI-mzQ\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"File\" id=\"bib-Uj-vzu\">\n                                    <items>\n                                        <menuItem title=\"New\" keyEquivalent=\"n\" id=\"Was-JA-tGl\">\n                                            <connections>\n                                                <action selector=\"newDocument:\" target=\"Ady-hI-5gd\" id=\"4Si-XN-c54\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Open…\" keyEquivalent=\"o\" id=\"IAo-SY-fd9\">\n                                            <connections>\n                                                <action selector=\"openDocument:\" target=\"Ady-hI-5gd\" id=\"bVn-NM-KNZ\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Open Recent\" id=\"tXI-mr-wws\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Open Recent\" systemMenu=\"recentDocuments\" id=\"oas-Oc-fiZ\">\n                                                <items>\n                                                    <menuItem title=\"Clear Menu\" id=\"vNY-rz-j42\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"clearRecentDocuments:\" target=\"Ady-hI-5gd\" id=\"Daa-9d-B3U\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"m54-Is-iLE\"/>\n                                        <menuItem title=\"Close\" keyEquivalent=\"w\" id=\"DVo-aG-piG\">\n                                            <connections>\n                                                <action selector=\"performClose:\" target=\"Ady-hI-5gd\" id=\"HmO-Ls-i7Q\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Save…\" keyEquivalent=\"s\" id=\"pxx-59-PXV\">\n                                            <connections>\n                                                <action selector=\"saveDocument:\" target=\"Ady-hI-5gd\" id=\"teZ-XB-qJY\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Save As…\" keyEquivalent=\"S\" id=\"Bw7-FT-i3A\">\n                                            <connections>\n                                                <action selector=\"saveDocumentAs:\" target=\"Ady-hI-5gd\" id=\"mDf-zr-I0C\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Revert to Saved\" keyEquivalent=\"r\" id=\"KaW-ft-85H\">\n                                            <connections>\n                                                <action selector=\"revertDocumentToSaved:\" target=\"Ady-hI-5gd\" id=\"iJ3-Pv-kwq\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"aJh-i4-bef\"/>\n                                        <menuItem title=\"Page Setup…\" keyEquivalent=\"P\" id=\"qIS-W8-SiK\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" shift=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"runPageLayout:\" target=\"Ady-hI-5gd\" id=\"Din-rz-gC5\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Print…\" keyEquivalent=\"p\" id=\"aTl-1u-JFS\">\n                                            <connections>\n                                                <action selector=\"print:\" target=\"Ady-hI-5gd\" id=\"qaZ-4w-aoO\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Edit\" id=\"5QF-Oa-p0T\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Edit\" id=\"W48-6f-4Dl\">\n                                    <items>\n                                        <menuItem title=\"Undo\" keyEquivalent=\"z\" id=\"dRJ-4n-Yzg\">\n                                            <connections>\n                                                <action selector=\"undo:\" target=\"Ady-hI-5gd\" id=\"M6e-cu-g7V\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Redo\" keyEquivalent=\"Z\" id=\"6dh-zS-Vam\">\n                                            <connections>\n                                                <action selector=\"redo:\" target=\"Ady-hI-5gd\" id=\"oIA-Rs-6OD\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"WRV-NI-Exz\"/>\n                                        <menuItem title=\"Cut\" keyEquivalent=\"x\" id=\"uRl-iY-unG\">\n                                            <connections>\n                                                <action selector=\"cut:\" target=\"Ady-hI-5gd\" id=\"YJe-68-I9s\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Copy\" keyEquivalent=\"c\" id=\"x3v-GG-iWU\">\n                                            <connections>\n                                                <action selector=\"copy:\" target=\"Ady-hI-5gd\" id=\"G1f-GL-Joy\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Paste\" keyEquivalent=\"v\" id=\"gVA-U4-sdL\">\n                                            <connections>\n                                                <action selector=\"paste:\" target=\"Ady-hI-5gd\" id=\"UvS-8e-Qdg\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Paste and Match Style\" keyEquivalent=\"V\" id=\"WeT-3V-zwk\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"pasteAsPlainText:\" target=\"Ady-hI-5gd\" id=\"cEh-KX-wJQ\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Delete\" id=\"pa3-QI-u2k\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"delete:\" target=\"Ady-hI-5gd\" id=\"0Mk-Ml-PaM\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Select All\" keyEquivalent=\"a\" id=\"Ruw-6m-B2m\">\n                                            <connections>\n                                                <action selector=\"selectAll:\" target=\"Ady-hI-5gd\" id=\"VNm-Mi-diN\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"uyl-h8-XO2\"/>\n                                        <menuItem title=\"Find\" id=\"4EN-yA-p0u\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Find\" id=\"1b7-l0-nxx\">\n                                                <items>\n                                                    <menuItem title=\"Find…\" tag=\"1\" keyEquivalent=\"f\" id=\"Xz5-n4-O0W\">\n                                                        <connections>\n                                                            <action selector=\"performFindPanelAction:\" target=\"Ady-hI-5gd\" id=\"cD7-Qs-BN4\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Find and Replace…\" tag=\"12\" keyEquivalent=\"f\" id=\"YEy-JH-Tfz\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                                        <connections>\n                                                            <action selector=\"performFindPanelAction:\" target=\"Ady-hI-5gd\" id=\"WD3-Gg-5AJ\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Find Next\" tag=\"2\" keyEquivalent=\"g\" id=\"q09-fT-Sye\">\n                                                        <connections>\n                                                            <action selector=\"performFindPanelAction:\" target=\"Ady-hI-5gd\" id=\"NDo-RZ-v9R\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Find Previous\" tag=\"3\" keyEquivalent=\"G\" id=\"OwM-mh-QMV\">\n                                                        <connections>\n                                                            <action selector=\"performFindPanelAction:\" target=\"Ady-hI-5gd\" id=\"HOh-sY-3ay\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Use Selection for Find\" tag=\"7\" keyEquivalent=\"e\" id=\"buJ-ug-pKt\">\n                                                        <connections>\n                                                            <action selector=\"performFindPanelAction:\" target=\"Ady-hI-5gd\" id=\"U76-nv-p5D\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Jump to Selection\" keyEquivalent=\"j\" id=\"S0p-oC-mLd\">\n                                                        <connections>\n                                                            <action selector=\"centerSelectionInVisibleArea:\" target=\"Ady-hI-5gd\" id=\"IOG-6D-g5B\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Spelling and Grammar\" id=\"Dv1-io-Yv7\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Spelling\" id=\"3IN-sU-3Bg\">\n                                                <items>\n                                                    <menuItem title=\"Show Spelling and Grammar\" keyEquivalent=\":\" id=\"HFo-cy-zxI\">\n                                                        <connections>\n                                                            <action selector=\"showGuessPanel:\" target=\"Ady-hI-5gd\" id=\"vFj-Ks-hy3\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Check Document Now\" keyEquivalent=\";\" id=\"hz2-CU-CR7\">\n                                                        <connections>\n                                                            <action selector=\"checkSpelling:\" target=\"Ady-hI-5gd\" id=\"fz7-VC-reM\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"bNw-od-mp5\"/>\n                                                    <menuItem title=\"Check Spelling While Typing\" id=\"rbD-Rh-wIN\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleContinuousSpellChecking:\" target=\"Ady-hI-5gd\" id=\"7w6-Qz-0kB\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Check Grammar With Spelling\" id=\"mK6-2p-4JG\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleGrammarChecking:\" target=\"Ady-hI-5gd\" id=\"muD-Qn-j4w\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Correct Spelling Automatically\" id=\"78Y-hA-62v\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticSpellingCorrection:\" target=\"Ady-hI-5gd\" id=\"2lM-Qi-WAP\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Substitutions\" id=\"9ic-FL-obx\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Substitutions\" id=\"FeM-D8-WVr\">\n                                                <items>\n                                                    <menuItem title=\"Show Substitutions\" id=\"z6F-FW-3nz\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"orderFrontSubstitutionsPanel:\" target=\"Ady-hI-5gd\" id=\"oku-mr-iSq\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"gPx-C9-uUO\"/>\n                                                    <menuItem title=\"Smart Copy/Paste\" id=\"9yt-4B-nSM\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleSmartInsertDelete:\" target=\"Ady-hI-5gd\" id=\"3IJ-Se-DZD\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Smart Quotes\" id=\"hQb-2v-fYv\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticQuoteSubstitution:\" target=\"Ady-hI-5gd\" id=\"ptq-xd-QOA\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Smart Dashes\" id=\"rgM-f4-ycn\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticDashSubstitution:\" target=\"Ady-hI-5gd\" id=\"oCt-pO-9gS\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Smart Links\" id=\"cwL-P1-jid\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticLinkDetection:\" target=\"Ady-hI-5gd\" id=\"Gip-E3-Fov\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Data Detectors\" id=\"tRr-pd-1PS\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticDataDetection:\" target=\"Ady-hI-5gd\" id=\"R1I-Nq-Kbl\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Text Replacement\" id=\"HFQ-gK-NFA\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticTextReplacement:\" target=\"Ady-hI-5gd\" id=\"DvP-Fe-Py6\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Transformations\" id=\"2oI-Rn-ZJC\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Transformations\" id=\"c8a-y6-VQd\">\n                                                <items>\n                                                    <menuItem title=\"Make Upper Case\" id=\"vmV-6d-7jI\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"uppercaseWord:\" target=\"Ady-hI-5gd\" id=\"sPh-Tk-edu\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Make Lower Case\" id=\"d9M-CD-aMd\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"lowercaseWord:\" target=\"Ady-hI-5gd\" id=\"iUZ-b5-hil\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Capitalize\" id=\"UEZ-Bs-lqG\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"capitalizeWord:\" target=\"Ady-hI-5gd\" id=\"26H-TL-nsh\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Speech\" id=\"xrE-MZ-jX0\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Speech\" id=\"3rS-ZA-NoH\">\n                                                <items>\n                                                    <menuItem title=\"Start Speaking\" id=\"Ynk-f8-cLZ\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"startSpeaking:\" target=\"Ady-hI-5gd\" id=\"654-Ng-kyl\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Stop Speaking\" id=\"Oyz-dy-DGm\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"stopSpeaking:\" target=\"Ady-hI-5gd\" id=\"dX8-6p-jy9\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Format\" id=\"jxT-CU-nIS\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Format\" id=\"GEO-Iw-cKr\">\n                                    <items>\n                                        <menuItem title=\"Font\" id=\"Gi5-1S-RQB\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Font\" systemMenu=\"font\" id=\"aXa-aM-Jaq\">\n                                                <items>\n                                                    <menuItem title=\"Show Fonts\" keyEquivalent=\"t\" id=\"Q5e-8K-NDq\">\n                                                        <connections>\n                                                            <action selector=\"orderFrontFontPanel:\" target=\"YLy-65-1bz\" id=\"WHr-nq-2xA\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Bold\" tag=\"2\" keyEquivalent=\"b\" id=\"GB9-OM-e27\">\n                                                        <connections>\n                                                            <action selector=\"addFontTrait:\" target=\"YLy-65-1bz\" id=\"hqk-hr-sYV\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Italic\" tag=\"1\" keyEquivalent=\"i\" id=\"Vjx-xi-njq\">\n                                                        <connections>\n                                                            <action selector=\"addFontTrait:\" target=\"YLy-65-1bz\" id=\"IHV-OB-c03\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Underline\" keyEquivalent=\"u\" id=\"WRG-CD-K1S\">\n                                                        <connections>\n                                                            <action selector=\"underline:\" target=\"Ady-hI-5gd\" id=\"FYS-2b-JAY\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"5gT-KC-WSO\"/>\n                                                    <menuItem title=\"Bigger\" tag=\"3\" keyEquivalent=\"+\" id=\"Ptp-SP-VEL\">\n                                                        <connections>\n                                                            <action selector=\"modifyFont:\" target=\"YLy-65-1bz\" id=\"Uc7-di-UnL\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Smaller\" tag=\"4\" keyEquivalent=\"-\" id=\"i1d-Er-qST\">\n                                                        <connections>\n                                                            <action selector=\"modifyFont:\" target=\"YLy-65-1bz\" id=\"HcX-Lf-eNd\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"kx3-Dk-x3B\"/>\n                                                    <menuItem title=\"Kern\" id=\"jBQ-r6-VK2\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <menu key=\"submenu\" title=\"Kern\" id=\"tlD-Oa-oAM\">\n                                                            <items>\n                                                                <menuItem title=\"Use Default\" id=\"GUa-eO-cwY\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"useStandardKerning:\" target=\"Ady-hI-5gd\" id=\"6dk-9l-Ckg\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Use None\" id=\"cDB-IK-hbR\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"turnOffKerning:\" target=\"Ady-hI-5gd\" id=\"U8a-gz-Maa\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Tighten\" id=\"46P-cB-AYj\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"tightenKerning:\" target=\"Ady-hI-5gd\" id=\"hr7-Nz-8ro\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Loosen\" id=\"ogc-rX-tC1\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"loosenKerning:\" target=\"Ady-hI-5gd\" id=\"8i4-f9-FKE\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                            </items>\n                                                        </menu>\n                                                    </menuItem>\n                                                    <menuItem title=\"Ligatures\" id=\"o6e-r0-MWq\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <menu key=\"submenu\" title=\"Ligatures\" id=\"w0m-vy-SC9\">\n                                                            <items>\n                                                                <menuItem title=\"Use Default\" id=\"agt-UL-0e3\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"useStandardLigatures:\" target=\"Ady-hI-5gd\" id=\"7uR-wd-Dx6\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Use None\" id=\"J7y-lM-qPV\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"turnOffLigatures:\" target=\"Ady-hI-5gd\" id=\"iX2-gA-Ilz\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Use All\" id=\"xQD-1f-W4t\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"useAllLigatures:\" target=\"Ady-hI-5gd\" id=\"KcB-kA-TuK\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                            </items>\n                                                        </menu>\n                                                    </menuItem>\n                                                    <menuItem title=\"Baseline\" id=\"OaQ-X3-Vso\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <menu key=\"submenu\" title=\"Baseline\" id=\"ijk-EB-dga\">\n                                                            <items>\n                                                                <menuItem title=\"Use Default\" id=\"3Om-Ey-2VK\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"unscript:\" target=\"Ady-hI-5gd\" id=\"0vZ-95-Ywn\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Superscript\" id=\"Rqc-34-cIF\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"superscript:\" target=\"Ady-hI-5gd\" id=\"3qV-fo-wpU\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Subscript\" id=\"I0S-gh-46l\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"subscript:\" target=\"Ady-hI-5gd\" id=\"Q6W-4W-IGz\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Raise\" id=\"2h7-ER-AoG\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"raiseBaseline:\" target=\"Ady-hI-5gd\" id=\"4sk-31-7Q9\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Lower\" id=\"1tx-W0-xDw\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"lowerBaseline:\" target=\"Ady-hI-5gd\" id=\"OF1-bc-KW4\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                            </items>\n                                                        </menu>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"Ndw-q3-faq\"/>\n                                                    <menuItem title=\"Show Colors\" keyEquivalent=\"C\" id=\"bgn-CT-cEk\">\n                                                        <connections>\n                                                            <action selector=\"orderFrontColorPanel:\" target=\"Ady-hI-5gd\" id=\"mSX-Xz-DV3\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"iMs-zA-UFJ\"/>\n                                                    <menuItem title=\"Copy Style\" keyEquivalent=\"c\" id=\"5Vv-lz-BsD\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                                        <connections>\n                                                            <action selector=\"copyFont:\" target=\"Ady-hI-5gd\" id=\"GJO-xA-L4q\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Paste Style\" keyEquivalent=\"v\" id=\"vKC-jM-MkH\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                                        <connections>\n                                                            <action selector=\"pasteFont:\" target=\"Ady-hI-5gd\" id=\"JfD-CL-leO\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Text\" id=\"Fal-I4-PZk\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Text\" id=\"d9c-me-L2H\">\n                                                <items>\n                                                    <menuItem title=\"Align Left\" keyEquivalent=\"{\" id=\"ZM1-6Q-yy1\">\n                                                        <connections>\n                                                            <action selector=\"alignLeft:\" target=\"Ady-hI-5gd\" id=\"zUv-R1-uAa\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Center\" keyEquivalent=\"|\" id=\"VIY-Ag-zcb\">\n                                                        <connections>\n                                                            <action selector=\"alignCenter:\" target=\"Ady-hI-5gd\" id=\"spX-mk-kcS\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Justify\" id=\"J5U-5w-g23\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"alignJustified:\" target=\"Ady-hI-5gd\" id=\"ljL-7U-jND\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Align Right\" keyEquivalent=\"}\" id=\"wb2-vD-lq4\">\n                                                        <connections>\n                                                            <action selector=\"alignRight:\" target=\"Ady-hI-5gd\" id=\"r48-bG-YeY\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"4s2-GY-VfK\"/>\n                                                    <menuItem title=\"Writing Direction\" id=\"H1b-Si-o9J\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <menu key=\"submenu\" title=\"Writing Direction\" id=\"8mr-sm-Yjd\">\n                                                            <items>\n                                                                <menuItem title=\"Paragraph\" enabled=\"NO\" id=\"ZvO-Gk-QUH\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                </menuItem>\n                                                                <menuItem id=\"YGs-j5-SAR\">\n                                                                    <string key=\"title\">\tDefault</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeBaseWritingDirectionNatural:\" target=\"Ady-hI-5gd\" id=\"qtV-5e-UBP\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem id=\"Lbh-J2-qVU\">\n                                                                    <string key=\"title\">\tLeft to Right</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeBaseWritingDirectionLeftToRight:\" target=\"Ady-hI-5gd\" id=\"S0X-9S-QSf\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem id=\"jFq-tB-4Kx\">\n                                                                    <string key=\"title\">\tRight to Left</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeBaseWritingDirectionRightToLeft:\" target=\"Ady-hI-5gd\" id=\"5fk-qB-AqJ\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem isSeparatorItem=\"YES\" id=\"swp-gr-a21\"/>\n                                                                <menuItem title=\"Selection\" enabled=\"NO\" id=\"cqv-fj-IhA\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                </menuItem>\n                                                                <menuItem id=\"Nop-cj-93Q\">\n                                                                    <string key=\"title\">\tDefault</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeTextWritingDirectionNatural:\" target=\"Ady-hI-5gd\" id=\"lPI-Se-ZHp\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem id=\"BgM-ve-c93\">\n                                                                    <string key=\"title\">\tLeft to Right</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeTextWritingDirectionLeftToRight:\" target=\"Ady-hI-5gd\" id=\"caW-Bv-w94\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem id=\"RB4-Sm-HuC\">\n                                                                    <string key=\"title\">\tRight to Left</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeTextWritingDirectionRightToLeft:\" target=\"Ady-hI-5gd\" id=\"EXD-6r-ZUu\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                            </items>\n                                                        </menu>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"fKy-g9-1gm\"/>\n                                                    <menuItem title=\"Show Ruler\" id=\"vLm-3I-IUL\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleRuler:\" target=\"Ady-hI-5gd\" id=\"FOx-HJ-KwY\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Copy Ruler\" keyEquivalent=\"c\" id=\"MkV-Pr-PK5\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                                        <connections>\n                                                            <action selector=\"copyRuler:\" target=\"Ady-hI-5gd\" id=\"71i-fW-3W2\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Paste Ruler\" keyEquivalent=\"v\" id=\"LVM-kO-fVI\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                                        <connections>\n                                                            <action selector=\"pasteRuler:\" target=\"Ady-hI-5gd\" id=\"cSh-wd-qM2\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"View\" id=\"H8h-7b-M4v\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"View\" id=\"HyV-fh-RgO\">\n                                    <items>\n                                        <menuItem title=\"Show Toolbar\" keyEquivalent=\"t\" id=\"snW-S8-Cw5\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"toggleToolbarShown:\" target=\"Ady-hI-5gd\" id=\"BXY-wc-z0C\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Customize Toolbar…\" id=\"1UK-8n-QPP\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"runToolbarCustomizationPalette:\" target=\"Ady-hI-5gd\" id=\"pQI-g3-MTW\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"hB3-LF-h0Y\"/>\n                                        <menuItem title=\"Show Sidebar\" keyEquivalent=\"s\" id=\"kIP-vf-haE\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"toggleSidebar:\" target=\"Ady-hI-5gd\" id=\"iwa-gc-5KM\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Enter Full Screen\" keyEquivalent=\"f\" id=\"4J7-dP-txa\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"toggleFullScreen:\" target=\"Ady-hI-5gd\" id=\"dU3-MA-1Rq\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Window\" id=\"aUF-d1-5bR\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Window\" systemMenu=\"window\" id=\"Td7-aD-5lo\">\n                                    <items>\n                                        <menuItem title=\"Minimize\" keyEquivalent=\"m\" id=\"OY7-WF-poV\">\n                                            <connections>\n                                                <action selector=\"performMiniaturize:\" target=\"Ady-hI-5gd\" id=\"VwT-WD-YPe\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Zoom\" id=\"R4o-n2-Eq4\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"performZoom:\" target=\"Ady-hI-5gd\" id=\"DIl-cC-cCs\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"eu3-7i-yIM\"/>\n                                        <menuItem title=\"Bring All to Front\" id=\"LE2-aR-0XJ\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"arrangeInFront:\" target=\"Ady-hI-5gd\" id=\"DRN-fu-gQh\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Help\" id=\"wpr-3q-Mcd\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Help\" systemMenu=\"help\" id=\"F2S-fz-NVQ\">\n                                    <items>\n                                        <menuItem title=\"piet-metal Help\" keyEquivalent=\"?\" id=\"FKE-Sm-Kum\">\n                                            <connections>\n                                                <action selector=\"showHelp:\" target=\"Ady-hI-5gd\" id=\"y7X-2Q-9no\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                        </items>\n                    </menu>\n                    <connections>\n                        <outlet property=\"delegate\" destination=\"Voe-Tx-rLC\" id=\"PrD-fu-P6m\"/>\n                    </connections>\n                </application>\n                <customObject id=\"Voe-Tx-rLC\" customClass=\"AppDelegate\"/>\n                <customObject id=\"YLy-65-1bz\" customClass=\"NSFontManager\"/>\n                <customObject id=\"Ady-hI-5gd\" userLabel=\"First Responder\" customClass=\"NSResponder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"75\" y=\"0.0\"/>\n        </scene>\n        <!--Window Controller-->\n        <scene sceneID=\"R2V-B0-nI4\">\n            <objects>\n                <windowController id=\"B8D-0N-5wS\" sceneMemberID=\"viewController\">\n                    <window key=\"window\" title=\"Window\" allowsToolTipsWhenApplicationIsInactive=\"NO\" autorecalculatesKeyViewLoop=\"NO\" releasedWhenClosed=\"NO\" visibleAtLaunch=\"NO\" animationBehavior=\"default\" id=\"IQv-IB-iLA\">\n                        <windowStyleMask key=\"styleMask\" titled=\"YES\" closable=\"YES\" miniaturizable=\"YES\" resizable=\"YES\"/>\n                        <windowPositionMask key=\"initialPositionMask\" leftStrut=\"YES\" rightStrut=\"YES\" topStrut=\"YES\" bottomStrut=\"YES\"/>\n                        <rect key=\"contentRect\" x=\"196\" y=\"240\" width=\"1024\" height=\"768\"/>\n                        <rect key=\"screenRect\" x=\"0.0\" y=\"0.0\" width=\"1680\" height=\"1027\"/>\n                        <connections>\n                            <outlet property=\"delegate\" destination=\"B8D-0N-5wS\" id=\"98r-iN-zZc\"/>\n                        </connections>\n                    </window>\n                    <connections>\n                        <segue destination=\"XfG-lQ-9wD\" kind=\"relationship\" relationship=\"window.shadowedContentViewController\" id=\"cq2-FE-JQM\"/>\n                    </connections>\n                </windowController>\n                <customObject id=\"Oky-zY-oP4\" userLabel=\"First Responder\" customClass=\"NSResponder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"75\" y=\"250\"/>\n        </scene>\n        <!--View Controller-->\n        <scene sceneID=\"hIz-AP-VOD\">\n            <objects>\n                <viewController id=\"XfG-lQ-9wD\" customClass=\"ViewController\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" id=\"m2S-Jp-Qdl\" customClass=\"MTKView\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"1024\" height=\"768\"/>\n                        <autoresizingMask key=\"autoresizingMask\"/>\n                    </view>\n                </viewController>\n                <customObject id=\"rPt-NT-nkU\" userLabel=\"First Responder\" customClass=\"NSResponder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"75\" y=\"655\"/>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "TestApp/GenTypes.h",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n// The contents of this file are autogenerated.\n\n// scene from new (merged) generator\n\ninline uint extract_8bit_value(uint bit_shift, uint package) {\n    uint mask = 255;\n    uint result = (package >> bit_shift) & mask;\n\n    return result;\n}\n\ninline uint extract_16bit_value(uint bit_shift, uint package) {\n    uint mask = 65535;\n    uint result = (package >> bit_shift) & mask;\n\n    return result;\n}\n\ntypedef uint SimpleGroupRef;\ntypedef uint PietCircleRef;\ntypedef uint PietStrokeLineRef;\ntypedef uint PietFillRef;\ntypedef uint PietStrokePolyLineRef;\ntypedef uint PietItemRef;\n\nstruct SimpleGroupPacked {\n    uint n_items;\n    PietItemRef items_ix;\n    uint2 bbox;\n};\n\ninline SimpleGroupPacked SimpleGroup_read(const device char *buf, SimpleGroupRef ref) {\n    SimpleGroupPacked result;\n\n    uint n_items = *(device const uint*)(buf + ref);\n    result.n_items = n_items;\n\n    PietItemRef items_ix = *(device const uint*)(buf + ref + 4);\n    result.items_ix = items_ix;\n\n    uint2 bbox = *(device const packed_uint2*)(buf + ref + 8);\n    result.bbox = bbox;\n\n    return result;\n}\n\ninline uint SimpleGroup_n_items(const device char *buf, SimpleGroupRef ref) {\n    uint n_items = *(device const uint*)(buf + ref);\n    return n_items;\n}\n\ninline PietItemRef SimpleGroup_items_ix(const device char *buf, SimpleGroupRef ref) {\n    PietItemRef items_ix = *(device const uint*)(buf + ref + 4);\n    return items_ix;\n}\n\ninline uint2 SimpleGroup_bbox(const device char *buf, SimpleGroupRef ref) {\n    uint2 bbox = *(device const packed_uint2*)(buf + ref + 8);\n    return bbox;\n}\n\ninline uint4 SimpleGroup_unpack_bbox(uint2 bbox) {\n    uint4 result;\n\n    result[0] = extract_16bit_value(0, bbox[0]);\n    result[1] = extract_16bit_value(16, bbox[0]);\n    result[2] = extract_16bit_value(0, bbox[1]);\n    result[3] = extract_16bit_value(16, bbox[1]);\n    return result;\n}\n\nstruct SimpleGroup {\n    uint n_items;\n    PietItemRef items_ix;\n    uint4 bbox;\n};\n\ninline SimpleGroup SimpleGroup_unpack(SimpleGroupPacked packed_form) {\n    SimpleGroup result;\n\n    result.n_items = packed_form.n_items;\n    result.items_ix = packed_form.items_ix;\n    result.bbox = SimpleGroup_unpack_bbox(packed_form.bbox);\n\n    return result;\n}\n\nstruct PietCirclePacked {\n    uint tag;\n};\n\ninline PietCirclePacked PietCircle_read(const device char *buf, PietCircleRef ref) {\n    PietCirclePacked result;\n\n    return result;\n}\n\nstruct PietCircle {\n};\n\ninline PietCircle PietCircle_unpack(PietCirclePacked packed_form) {\n    PietCircle result;\n\n\n    return result;\n}\n\nstruct PietStrokeLinePacked {\n    uint tag;\n    uint flags;\n    uint rgba_color;\n    float width;\n    float2 start;\n    float2 end;\n};\n\ninline PietStrokeLinePacked PietStrokeLine_read(const device char *buf, PietStrokeLineRef ref) {\n    PietStrokeLinePacked result;\n\n    uint flags = *(device const uint*)(buf + ref + 4);\n    result.flags = flags;\n\n    uint rgba_color = *(device const uint*)(buf + ref + 8);\n    result.rgba_color = rgba_color;\n\n    float width = as_type<float>(*(device const uint*)(buf + ref + 12));\n    result.width = width;\n\n    float2 start = as_type<float2>(*(device const packed_uint2*)(buf + ref + 16));\n    result.start = start;\n\n    float2 end = as_type<float2>(*(device const packed_uint2*)(buf + ref + 24));\n    result.end = end;\n\n    return result;\n}\n\ninline uint PietStrokeLine_flags(const device char *buf, PietStrokeLineRef ref) {\n    uint flags = *(device const uint*)(buf + ref + 4);\n    return flags;\n}\n\ninline uint PietStrokeLine_rgba_color(const device char *buf, PietStrokeLineRef ref) {\n    uint rgba_color = *(device const uint*)(buf + ref + 8);\n    return rgba_color;\n}\n\ninline float PietStrokeLine_width(const device char *buf, PietStrokeLineRef ref) {\n    float width = as_type<float>(*(device const uint*)(buf + ref + 12));\n    return width;\n}\n\ninline float2 PietStrokeLine_start(const device char *buf, PietStrokeLineRef ref) {\n    float2 start = as_type<float2>(*(device const packed_uint2*)(buf + ref + 16));\n    return start;\n}\n\ninline float2 PietStrokeLine_end(const device char *buf, PietStrokeLineRef ref) {\n    float2 end = as_type<float2>(*(device const packed_uint2*)(buf + ref + 24));\n    return end;\n}\n\nstruct PietStrokeLine {\n    uint flags;\n    uint rgba_color;\n    float width;\n    float2 start;\n    float2 end;\n};\n\ninline PietStrokeLine PietStrokeLine_unpack(PietStrokeLinePacked packed_form) {\n    PietStrokeLine result;\n\n    result.flags = packed_form.flags;\n    result.rgba_color = packed_form.rgba_color;\n    result.width = packed_form.width;\n    result.start = packed_form.start;\n    result.end = packed_form.end;\n\n    return result;\n}\n\nstruct PietFillPacked {\n    uint tag;\n    uint flags;\n    uint rgba_color;\n    uint n_points;\n    uint points_ix;\n};\n\ninline PietFillPacked PietFill_read(const device char *buf, PietFillRef ref) {\n    PietFillPacked result;\n\n    uint flags = *(device const uint*)(buf + ref + 4);\n    result.flags = flags;\n\n    uint rgba_color = *(device const uint*)(buf + ref + 8);\n    result.rgba_color = rgba_color;\n\n    uint n_points = *(device const uint*)(buf + ref + 12);\n    result.n_points = n_points;\n\n    uint points_ix = *(device const uint*)(buf + ref + 16);\n    result.points_ix = points_ix;\n\n    return result;\n}\n\ninline uint PietFill_flags(const device char *buf, PietFillRef ref) {\n    uint flags = *(device const uint*)(buf + ref + 4);\n    return flags;\n}\n\ninline uint PietFill_rgba_color(const device char *buf, PietFillRef ref) {\n    uint rgba_color = *(device const uint*)(buf + ref + 8);\n    return rgba_color;\n}\n\ninline uint PietFill_n_points(const device char *buf, PietFillRef ref) {\n    uint n_points = *(device const uint*)(buf + ref + 12);\n    return n_points;\n}\n\ninline uint PietFill_points_ix(const device char *buf, PietFillRef ref) {\n    uint points_ix = *(device const uint*)(buf + ref + 16);\n    return points_ix;\n}\n\nstruct PietFill {\n    uint flags;\n    uint rgba_color;\n    uint n_points;\n    uint points_ix;\n};\n\ninline PietFill PietFill_unpack(PietFillPacked packed_form) {\n    PietFill result;\n\n    result.flags = packed_form.flags;\n    result.rgba_color = packed_form.rgba_color;\n    result.n_points = packed_form.n_points;\n    result.points_ix = packed_form.points_ix;\n\n    return result;\n}\n\nstruct PietStrokePolyLinePacked {\n    uint tag;\n    uint rgba_color;\n    float width;\n    uint n_points;\n    uint points_ix;\n};\n\ninline PietStrokePolyLinePacked PietStrokePolyLine_read(const device char *buf, PietStrokePolyLineRef ref) {\n    PietStrokePolyLinePacked result;\n\n    uint rgba_color = *(device const uint*)(buf + ref + 4);\n    result.rgba_color = rgba_color;\n\n    float width = as_type<float>(*(device const uint*)(buf + ref + 8));\n    result.width = width;\n\n    uint n_points = *(device const uint*)(buf + ref + 12);\n    result.n_points = n_points;\n\n    uint points_ix = *(device const uint*)(buf + ref + 16);\n    result.points_ix = points_ix;\n\n    return result;\n}\n\ninline uint PietStrokePolyLine_rgba_color(const device char *buf, PietStrokePolyLineRef ref) {\n    uint rgba_color = *(device const uint*)(buf + ref + 4);\n    return rgba_color;\n}\n\ninline float PietStrokePolyLine_width(const device char *buf, PietStrokePolyLineRef ref) {\n    float width = as_type<float>(*(device const uint*)(buf + ref + 8));\n    return width;\n}\n\ninline uint PietStrokePolyLine_n_points(const device char *buf, PietStrokePolyLineRef ref) {\n    uint n_points = *(device const uint*)(buf + ref + 12);\n    return n_points;\n}\n\ninline uint PietStrokePolyLine_points_ix(const device char *buf, PietStrokePolyLineRef ref) {\n    uint points_ix = *(device const uint*)(buf + ref + 16);\n    return points_ix;\n}\n\nstruct PietStrokePolyLine {\n    uint rgba_color;\n    float width;\n    uint n_points;\n    uint points_ix;\n};\n\ninline PietStrokePolyLine PietStrokePolyLine_unpack(PietStrokePolyLinePacked packed_form) {\n    PietStrokePolyLine result;\n\n    result.rgba_color = packed_form.rgba_color;\n    result.width = packed_form.width;\n    result.n_points = packed_form.n_points;\n    result.points_ix = packed_form.points_ix;\n\n    return result;\n}\n\nstruct PietItem {\n    uint tag;\n    uint body[7];\n};\ninline uint PietItem_tag(const device char *buf, PietItemRef ref) {\n    uint result = *(device const uint*)(buf + ref);\n    return result;\n}\n\n#define SIMPLE_GROUP_SIZE 16\n#define PIET_ITEM_SIZE 32\n// TODO: these are manually fixed up. Make encoders consistent\n#define PietItem_Circle 1\n#define PietItem_Line 2\n#define PietItem_Fill 3\n#define PietItem_Poly 4\n\n// Following are older-style accessors (haven't converted ptcl yet)\n\ntypedef uint CmdCircleRef;\ntypedef uint CmdLineRef;\ntypedef uint CmdStrokeRef;\ntypedef uint CmdFillRef;\ntypedef uint CmdFillEdgeRef;\ntypedef uint CmdDrawFillRef;\ntypedef uint CmdSolidRef;\ntypedef uint CmdRef;\nstruct CmdCirclePacked {\n    uint tag;\n    ushort4 bbox;\n};\nCmdCirclePacked CmdCircle_read(const device char *buf, CmdCircleRef ref) {\n    return *((const device CmdCirclePacked *)(buf + ref));\n}\nushort4 CmdCircle_bbox(const device char *buf, CmdCircleRef ref) {\n    return ((const device CmdCirclePacked *)(buf + ref))->bbox;\n}\nstruct CmdLinePacked {\n    uint tag;\n    float2 start;\n    float2 end;\n};\nCmdLinePacked CmdLine_read(const device char *buf, CmdLineRef ref) {\n    return *((const device CmdLinePacked *)(buf + ref));\n}\nfloat2 CmdLine_start(const device char *buf, CmdLineRef ref) {\n    return ((const device CmdLinePacked *)(buf + ref))->start;\n}\nfloat2 CmdLine_end(const device char *buf, CmdLineRef ref) {\n    return ((const device CmdLinePacked *)(buf + ref))->end;\n}\nstruct CmdStrokePacked {\n    uint tag;\n    float halfWidth;\n    uint rgba_color;\n};\nCmdStrokePacked CmdStroke_read(const device char *buf, CmdStrokeRef ref) {\n    return *((const device CmdStrokePacked *)(buf + ref));\n}\nfloat CmdStroke_halfWidth(const device char *buf, CmdStrokeRef ref) {\n    return ((const device CmdStrokePacked *)(buf + ref))->halfWidth;\n}\nuint CmdStroke_rgba_color(const device char *buf, CmdStrokeRef ref) {\n    return ((const device CmdStrokePacked *)(buf + ref))->rgba_color;\n}\nstruct CmdFillPacked {\n    uint tag;\n    float2 start;\n    float2 end;\n};\nCmdFillPacked CmdFill_read(const device char *buf, CmdFillRef ref) {\n    return *((const device CmdFillPacked *)(buf + ref));\n}\nfloat2 CmdFill_start(const device char *buf, CmdFillRef ref) {\n    return ((const device CmdFillPacked *)(buf + ref))->start;\n}\nfloat2 CmdFill_end(const device char *buf, CmdFillRef ref) {\n    return ((const device CmdFillPacked *)(buf + ref))->end;\n}\nstruct CmdFillEdgePacked {\n    uint tag;\n    int sign;\n    float y;\n};\nCmdFillEdgePacked CmdFillEdge_read(const device char *buf, CmdFillEdgeRef ref) {\n    return *((const device CmdFillEdgePacked *)(buf + ref));\n}\nint CmdFillEdge_sign(const device char *buf, CmdFillEdgeRef ref) {\n    return ((const device CmdFillEdgePacked *)(buf + ref))->sign;\n}\nfloat CmdFillEdge_y(const device char *buf, CmdFillEdgeRef ref) {\n    return ((const device CmdFillEdgePacked *)(buf + ref))->y;\n}\nstruct CmdDrawFillPacked {\n    uint tag;\n    int backdrop;\n    uint rgba_color;\n};\nCmdDrawFillPacked CmdDrawFill_read(const device char *buf, CmdDrawFillRef ref) {\n    return *((const device CmdDrawFillPacked *)(buf + ref));\n}\nint CmdDrawFill_backdrop(const device char *buf, CmdDrawFillRef ref) {\n    return ((const device CmdDrawFillPacked *)(buf + ref))->backdrop;\n}\nuint CmdDrawFill_rgba_color(const device char *buf, CmdDrawFillRef ref) {\n    return ((const device CmdDrawFillPacked *)(buf + ref))->rgba_color;\n}\nstruct CmdSolidPacked {\n    uint tag;\n    uint rgba_color;\n};\nCmdSolidPacked CmdSolid_read(const device char *buf, CmdSolidRef ref) {\n    return *((const device CmdSolidPacked *)(buf + ref));\n}\nuint CmdSolid_rgba_color(const device char *buf, CmdSolidRef ref) {\n    return ((const device CmdSolidPacked *)(buf + ref))->rgba_color;\n}\nstruct Cmd {\n    uint tag;\n    uint body[5];\n};\nCmd Cmd_read(const device char *buf, CmdRef ref) {\n    return *((const device Cmd *)(buf + ref));\n}\nuint Cmd_tag(const device char *buf, CmdRef ref) {\n    return ((const device Cmd *)(buf + ref))->tag;\n}\n#define Cmd_End 1\n#define Cmd_Circle 2\nCmdCirclePacked CmdCircle_load(const thread Cmd &s) {\n    CmdCirclePacked r;\n    r.tag = s.tag;\n    r.bbox = *((const thread ushort4 *)((const thread char *)&s + 8));\n    return r;\n}\n#define Cmd_Line 3\nCmdLinePacked CmdLine_load(const thread Cmd &s) {\n    CmdLinePacked r;\n    r.tag = s.tag;\n    r.start = *((const thread float2 *)((const thread char *)&s + 8));\n    r.end = *((const thread float2 *)((const thread char *)&s + 16));\n    return r;\n}\n#define Cmd_Fill 4\nCmdFillPacked CmdFill_load(const thread Cmd &s) {\n    CmdFillPacked r;\n    r.tag = s.tag;\n    r.start = *((const thread float2 *)((const thread char *)&s + 8));\n    r.end = *((const thread float2 *)((const thread char *)&s + 16));\n    return r;\n}\n#define Cmd_Stroke 5\nCmdStrokePacked CmdStroke_load(const thread Cmd &s) {\n    CmdStrokePacked r;\n    r.tag = s.tag;\n    r.halfWidth = *((const thread float *)((const thread char *)&s + 4));\n    r.rgba_color = *((const thread uint *)((const thread char *)&s + 8));\n    return r;\n}\n#define Cmd_FillEdge 6\nCmdFillEdgePacked CmdFillEdge_load(const thread Cmd &s) {\n    CmdFillEdgePacked r;\n    r.tag = s.tag;\n    r.sign = *((const thread int *)((const thread char *)&s + 4));\n    r.y = *((const thread float *)((const thread char *)&s + 8));\n    return r;\n}\n#define Cmd_DrawFill 7\nCmdDrawFillPacked CmdDrawFill_load(const thread Cmd &s) {\n    CmdDrawFillPacked r;\n    r.tag = s.tag;\n    r.backdrop = *((const thread int *)((const thread char *)&s + 4));\n    r.rgba_color = *((const thread uint *)((const thread char *)&s + 8));\n    return r;\n}\n#define Cmd_Solid 8\nCmdSolidPacked CmdSolid_load(const thread Cmd &s) {\n    CmdSolidPacked r;\n    r.tag = s.tag;\n    r.rgba_color = *((const thread uint *)((const thread char *)&s + 4));\n    return r;\n}\n#define Cmd_Bail 9\n\nvoid CmdCircle_write(device char *buf, CmdCircleRef ref, CmdCirclePacked s) {\n    *((device CmdCirclePacked *)(buf + ref)) = s;\n}\nvoid CmdLine_write(device char *buf, CmdLineRef ref, CmdLinePacked s) {\n    *((device CmdLinePacked *)(buf + ref)) = s;\n}\nvoid CmdStroke_write(device char *buf, CmdStrokeRef ref, CmdStrokePacked s) {\n    *((device CmdStrokePacked *)(buf + ref)) = s;\n}\nvoid CmdFill_write(device char *buf, CmdFillRef ref, CmdFillPacked s) {\n    *((device CmdFillPacked *)(buf + ref)) = s;\n}\nvoid CmdFillEdge_write(device char *buf, CmdFillEdgeRef ref, CmdFillEdgePacked s) {\n    *((device CmdFillEdgePacked *)(buf + ref)) = s;\n}\nvoid CmdDrawFill_write(device char *buf, CmdDrawFillRef ref, CmdDrawFillPacked s) {\n    *((device CmdDrawFillPacked *)(buf + ref)) = s;\n}\nvoid CmdSolid_write(device char *buf, CmdSolidRef ref, CmdSolidPacked s) {\n    *((device CmdSolidPacked *)(buf + ref)) = s;\n}\nvoid Cmd_write_tag(device char *buf, CmdRef ref, uint tag) {\n    ((device Cmd *)(buf + ref))->tag = tag;\n}\n\n"
  },
  {
    "path": "TestApp/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIconFile</key>\n\t<string></string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>$(MACOSX_DEPLOYMENT_TARGET)</string>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>Copyright 2019 The xi-editor authors.</string>\n\t<key>NSMainStoryboardFile</key>\n\t<string>Main</string>\n\t<key>NSPrincipalClass</key>\n\t<string>NSApplication</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "TestApp/PietRender.metal",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n#include <metal_stdlib>\nusing namespace metal;\n\n#include \"PietShaderTypes.h\"\n#include \"GenTypes.h\"\n\nstruct RenderData {\n    float4 clipSpacePosition [[position]];\n    float2 textureCoordinate;\n    float pointSize [[point_size]];\n    half4 solidColor;\n};\n\nvertex RenderData\nvertexShader(uint vertexID [[ vertex_id ]],\n             constant RenderVertex *vertexArray [[ buffer(RenderVertexInputIndexVertices) ]],\n             texture2d<half> loTexture [[texture(0)]])\n{\n    RenderData out;\n    float2 clipSpacePosition = vertexArray[vertexID].position;\n    out.clipSpacePosition.xy = clipSpacePosition;\n    out.clipSpacePosition.z = 0.0;\n    out.clipSpacePosition.w = 1.0;\n    float2 xy = vertexArray[vertexID].textureCoordinate;\n    out.textureCoordinate = xy;\n    out.pointSize = 16;\n    uint2 tileXY = uint2(xy.x / tileWidth, xy.y / tileHeight);\n    out.solidColor = loTexture.read(tileXY);\n    return out;\n}\n\nfragment half4 fragmentShader(RenderData in [[stage_in]],\n                               texture2d<half> texture [[texture(0)]]) {\n    const half4 loSample = in.solidColor;\n    if (loSample.a == 0.0) {\n        uint2 coords = uint2(in.clipSpacePosition.xy);\n        const half4 sample = texture.read(coords);\n        return sample;\n    } else {\n        return loSample;\n    }\n}\n\n// Distance field rendering of strokes\n\n// Accumulate distance field.\nvoid stroke(thread float &df, float2 pos, float2 start, float2 end) {\n    float2 lineVec = end - start;\n    float2 dPos = pos - start;\n    float t = saturate(dot(lineVec, dPos) / dot(lineVec, lineVec));\n    float field = length(lineVec * t - dPos);\n    df = min(df, field);\n}\n\n// TODO: figure out precision so we can move more stuff to half\nhalf renderDf(float df, float halfWidth) {\n    return saturate(halfWidth + 0.5 - df);\n}\n\n/*\n// TODO: this should be in the autogenerated output\nvoid Cmd_write_tag(device char *buf, CmdRef ref, uint tag) {\n    ((device Cmd *)(buf + ref))->tag = tag;\n}\n */\n\nstruct TileEncoder {\npublic:\n    TileEncoder(device char *dst) {\n        this->dst = dst;\n        this->tileBegin = dst;\n        this->solidColor = 0xffffffff;\n    }\n    void encodeCircle(ushort4 bbox) {\n        CmdCirclePacked cmd;\n        cmd.tag = Cmd_Circle;\n        cmd.bbox = bbox;\n        CmdCircle_write(dst, 0, cmd);\n        solidColor = 0;\n        dst += sizeof(Cmd);\n    }\n    void encodeLine(float2 start, float2 end) {\n        CmdLinePacked cmd;\n        cmd.tag = Cmd_Line;\n        cmd.start = start;\n        cmd.end = end;\n        CmdLine_write(dst, 0, cmd);\n        solidColor = 0;\n        dst += sizeof(Cmd);\n    }\n    void encodeStroke(uint rgbaColor, float width) {\n        CmdStrokePacked cmd;\n        cmd.tag = Cmd_Stroke;\n        cmd.rgba_color = rgbaColor;\n        cmd.halfWidth = 0.5 * width;\n        CmdStroke_write(dst, 0, cmd);\n        solidColor = 0;\n        dst += sizeof(Cmd);\n    }\n    void encodeFill(float2 start, float2 end) {\n        CmdFillPacked cmd;\n        cmd.tag = Cmd_Fill;\n        cmd.start = start;\n        cmd.end = end;\n        CmdFill_write(dst, 0, cmd);\n        dst += sizeof(Cmd);\n    }\n    void encodeFillEdge(float sign, float y) {\n        CmdFillEdgePacked cmd;\n        cmd.tag = Cmd_FillEdge;\n        cmd.sign = sign;\n        cmd.y = y;\n        CmdFillEdge_write(dst, 0, cmd);\n        dst += sizeof(Cmd);\n    }\n    void encodeDrawFill(const thread PietFillPacked &fill, int backdrop) {\n        CmdDrawFillPacked cmd;\n        cmd.tag = Cmd_DrawFill;\n        cmd.backdrop = backdrop;\n        cmd.rgba_color = fill.rgba_color;\n        CmdDrawFill_write(dst, 0, cmd);\n        solidColor = 0;\n        dst += sizeof(Cmd);\n    }\n    void encodeSolid(uint rgba) {\n        // A fun optimization would be to alpha-composite semi-opaque\n        // solid blocks.\n        \n        // Another optimization is to skip encoding the default bg color.\n        if ((rgba & 0xff000000) == 0xff000000) {\n            solidColor = rgba;\n            dst = tileBegin;\n        }\n        CmdSolidPacked cmd;\n        // Note: could defer writing, not sure how much of a win that is\n        cmd.tag = Cmd_Solid;\n        cmd.rgba_color = rgba;\n        CmdSolid_write(dst, 0, cmd);\n        dst += sizeof(Cmd);\n    }\n    // return solid color\n    uint end() {\n        if (solidColor) {\n            Cmd_write_tag(tileBegin, 0, Cmd_Bail);\n        } else {\n            Cmd_write_tag(dst, 0, Cmd_End);\n        }\n        return solidColor;\n    }\nprivate:\n    // Pointer to command buffer for tile.\n    device char *dst;\n    device char *tileBegin;\n    uint solidColor;\n};\n\n// Traverse the scene graph and produce a command list for a tile.\nkernel void\ntileKernel(device const char *scene [[buffer(0)]],\n           device char *tiles [[buffer(1)]],\n           texture2d<half, access::write> outTexture [[texture(0)]],\n           uint2 gid [[thread_position_in_grid]],\n           uint tix [[thread_index_in_threadgroup]])\n{\n    uint tileIx = gid.y * maxTilesWidth + gid.x;\n    ushort x0 = gid.x * tileWidth;\n    ushort y0 = gid.y * tileHeight;\n    device char *dst = tiles + tileIx * tileBufSize;\n    TileEncoder encoder(dst);\n    // TODO: correct calculation of size\n    const ushort tgs = tilerGroupWidth * tilerGroupHeight;\n    const ushort nBitmap = tgs / 32;\n    threadgroup atomic_uint bitmap;\n    threadgroup uint rd;\n    const memory_order relaxed = memory_order::memory_order_relaxed;\n\n    // Size of the region covered by one SIMD group. TODO, don't hardcode.\n    const ushort stw = tilerGroupWidth * tileWidth;\n    const ushort sth = tilerGroupHeight * tileHeight;\n    ushort sx0 = x0 & ~(stw - 1);\n    ushort sy0 = y0 & ~(sth - 1);\n    \n    SimpleGroupRef group_ref = 0;\n    // TODO: write accessor functions for variable-sized array here\n    device const SimpleGroupPacked *group = (device const SimpleGroupPacked *)scene;\n    device const ushort4 *bboxes = (device const ushort4 *)&group->bbox;\n    uint n = SimpleGroup_n_items(scene, group_ref);\n    PietItemRef items_ref = SimpleGroup_items_ix(scene, group_ref);\n    for (uint i = 0; i < n; i += tgs) {\n        if (tix < nBitmap) {\n            atomic_store_explicit(&bitmap, 0, relaxed);\n        }\n        threadgroup_barrier(mem_flags::mem_threadgroup);\n        if (i + tix < n) {\n            ushort4 bbox = bboxes[i + tix];\n            if (bbox.z >= sx0 && bbox.x < sx0 + stw && bbox.w >= sy0 && bbox.y < sy0 + sth) {\n                uint mask = 1 << (tix & 31);\n                atomic_fetch_or_explicit(&bitmap, mask, relaxed);\n            }\n        }\n        threadgroup_barrier(mem_flags::mem_threadgroup);\n        if (tix == 0) {\n            rd = atomic_load_explicit(&bitmap, relaxed);\n            atomic_store_explicit(&bitmap, 0, relaxed);\n        }\n        threadgroup_barrier(mem_flags::mem_threadgroup);\n        //for (ushort bitmapIx = 0; bitmapIx <= bitmapCount; bitmapIx++) {\n            uint v = rd;\n            while (v) {\n                uint ix = i + ctz(v);\n                ushort4 bbox = bboxes[ix];\n                bool hit = bbox.z >= x0 && bbox.x < x0 + tileWidth && bbox.w >= y0 && bbox.y < y0 + tileHeight;\n                PietItemRef item_ref = items_ref + ix * sizeof(PietItem);\n                ushort itemType = PietItem_tag(scene, item_ref);\n                switch (itemType) {\n                    case PietItem_Circle:\n                        if (hit) {\n                            encoder.encodeCircle(bbox);\n                        }\n                        break;\n                    case PietItem_Line: {\n                        // set up line equation, ax + by + c = 0\n                        if (hit) {\n                            PietStrokeLinePacked line = PietStrokeLine_read(scene, item_ref);\n                            float a = line.end.y - line.start.y;\n                            float b = line.start.x - line.end.x;\n                            float c = -(a * line.start.x + b * line.start.y);\n                            // TODO: is this bound as tight as it can be?\n                            float hw = 0.5 * line.width + 0.5;\n                            float left = a * (x0 - hw);\n                            float right = a * (x0 + tileWidth + hw);\n                            float top = b * (y0 - hw);\n                            float bot = b * (y0 + tileHeight + hw);\n                            // If all four corners are on same side of line, cull\n                            float s00 = sign(top + left + c);\n                            float s01 = sign(top + right + c);\n                            float s10 = sign(bot + left + c);\n                            float s11 = sign(bot + right + c);\n                            if (s00 * s01 + s00 * s10 + s00 * s11 < 3.0) {\n                                encoder.encodeLine(line.start, line.end);\n                                encoder.encodeStroke(line.rgba_color, line.width);\n                            }\n                        }\n                        break;\n                    }\n                    case PietItem_Fill: {\n                        PietFillPacked fill = PietFill_read(scene, item_ref);\n                        device const float2 *pts = (device const float2 *)(scene + fill.points_ix);\n                        uint nPoints = fill.n_points;\n                        float backdrop = 0;\n                        bool anyFill = false;\n                        // use simd ballot to quick-reject segments with no contribution\n                        // Note: we just do 16 at a time for now, there's the option of doing\n                        // a 16x2 strip of tiles, with more complexity in the left-ray test.\n                        for (uint j = 0; j < nPoints; j += 16) {\n                            bool fillHit = false;\n                            uint fillIx = j + (tix & 15);\n                            if (fillIx < nPoints) {\n                                float2 start = pts[fillIx];\n                                float2 end = pts[fillIx + 1 == nPoints ? 0 : fillIx + 1];\n                                float2 xymin = min(start, end);\n                                float2 xymax = max(start, end);\n                                if (xymax.y >= y0 && xymin.y < y0 + tileHeight && xymin.x < sx0 + stw) {\n                                    // set up line equation, ax + by + c = 0\n                                    float a = end.y - start.y;\n                                    float b = start.x - end.x;\n                                    float c = -(a * start.x + b * start.y);\n                                    float left = a * sx0;\n                                    float right = a * (sx0 + stw);\n                                    float ytop = max(float(y0), xymin.y);\n                                    float ybot = min(float(y0 + tileHeight), xymax.y);\n                                    float top = b * ytop;\n                                    float bot = b * ybot;\n                                    // top left of rightmost tile in strip\n                                    float sTopLeft = sign(right - a * (tileWidth) + float(y0) * b + c);\n                                    float s00 = sign(top + left + c);\n                                    float s01 = sign(top + right + c);\n                                    float s10 = sign(bot + left + c);\n                                    float s11 = sign(bot + right + c);\n                                    if (sTopLeft == sign(a) && xymin.y <= y0) {\n                                        // left ray intersects, need backdrop\n                                        fillHit = true;\n                                    }\n                                    if (s00 * s01 + s00 * s10 + s00 * s11 < 3.0 && xymax.x > sx0) {\n                                        // intersects strip\n                                        fillHit = true;\n                                    }\n                                    // TODO: maybe avoid boolean - does it cost a register?\n                                    if (fillHit) {\n                                        atomic_fetch_or_explicit(&bitmap, 1 << tix, relaxed);\n                                    }\n                                }\n                            }\n                            threadgroup_barrier(mem_flags::mem_threadgroup);\n                            if (tix == 0) {\n                                rd = atomic_load_explicit(&bitmap, relaxed);\n                                atomic_store_explicit(&bitmap, 0, relaxed);\n                            }\n                            threadgroup_barrier(mem_flags::mem_threadgroup);\n                            uint fillVote = (rd >> (tix & 16)) & 0xffff;\n                            while (fillVote) {\n                                uint fillSubIx = ctz(fillVote);\n                                fillIx = j + fillSubIx;\n\n                                if (hit) {\n                                    float2 start = pts[fillIx];\n                                    float2 end = pts[fillIx + 1 == nPoints ? 0 : fillIx + 1];\n                                    float2 xymin = min(start, end);\n                                    float2 xymax = max(start, end);\n                                    // Note: no y-based cull here because it's been done in the earlier pass.\n                                    // If we change that to do a strip taller than 1 tile, re-introduce here.\n\n                                    // set up line equation, ax + by + c = 0\n                                    float a = end.y - start.y;\n                                    float b = start.x - end.x;\n                                    float c = -(a * start.x + b * start.y);\n                                    float left = a * x0;\n                                    float right = a * (x0 + tileWidth);\n                                    float ytop = max(float(y0), xymin.y);\n                                    float ybot = min(float(y0 + tileHeight), xymax.y);\n                                    float top = b * ytop;\n                                    float bot = b * ybot;\n                                    // top left of tile\n                                    float sTopLeft = sign(left + float(y0) * b + c);\n                                    float s00 = sign(top + left + c);\n                                    float s01 = sign(top + right + c);\n                                    float s10 = sign(bot + left + c);\n                                    float s11 = sign(bot + right + c);\n                                    if (sTopLeft == sign(a) && xymin.y <= y0) {\n                                        backdrop -= s00;\n                                    }\n                                    if (xymin.x < x0 && xymax.x > x0) {\n                                        float yEdge = mix(start.y, end.y, (start.x - x0) / b);\n                                        if (yEdge >= y0 && yEdge < y0 + tileHeight) {\n                                            // line intersects left edge of this tile\n                                            encoder.encodeFillEdge(s00, yEdge);\n                                            if (b > 0.0) {\n                                                encoder.encodeFill(start, float2(x0, yEdge));\n                                            } else {\n                                                encoder.encodeFill(float2(x0, yEdge), end);\n                                            }\n                                            anyFill = true;\n                                        } else if (s00 * s01 + s00 * s10 + s00 * s11 < 3.0) {\n                                            encoder.encodeFill(start, end);\n                                            anyFill = true;\n                                        }\n                                    } else if (s00 * s01 + s00 * s10 + s00 * s11 < 3.0\n                                               && xymin.x < x0 + tileWidth && xymax.x > x0) {\n                                        encoder.encodeFill(start, end);\n                                        anyFill = true;\n                                    }\n                                } // end if (hit)\n\n                                fillVote &= ~(1 << fillSubIx);\n                            }\n                        }\n                        if (anyFill) {\n                            encoder.encodeDrawFill(fill, backdrop);\n                        } else if (backdrop != 0.0) {\n                            encoder.encodeSolid(fill.rgba_color);\n                        }\n                        break;\n                    }\n                    case PietItem_Poly: {\n                        PietStrokePolyLinePacked poly = PietStrokePolyLine_read(scene, item_ref);\n                        device const float2 *pts = (device const float2 *)(scene + poly.points_ix);\n                        uint nPoints = poly.n_points - 1;\n                        bool anyStroke = false;\n                        float hw = 0.5 * poly.width + 0.5;\n                        // use simd ballot to quick-reject segments with no contribution\n                        for (uint j = 0; j < nPoints; j += 32) {\n                            uint polyIx = j + tix;\n                            if (polyIx < nPoints) {\n                                float2 start = pts[polyIx];\n                                float2 end = pts[polyIx + 1];\n                                float2 xymin = min(start, end);\n                                float2 xymax = max(start, end);\n                                if (xymax.y > sy0 - hw && xymin.y < sy0 + sth + hw &&\n                                    xymax.x > sx0 - hw && xymin.x < sx0 + stw + hw) {\n                                    // set up line equation, ax + by + c = 0\n                                    float a = end.y - start.y;\n                                    float b = start.x - end.x;\n                                    float c = -(a * start.x + b * start.y);\n                                    float left = a * (sx0 - hw);\n                                    float right = a * (sx0 + stw + hw);\n                                    float top = b * (y0 - hw);\n                                    float bot = b * (y0 + tileHeight + hw);\n                                    float s00 = sign(top + left + c);\n                                    float s01 = sign(top + right + c);\n                                    float s10 = sign(bot + left + c);\n                                    float s11 = sign(bot + right + c);\n                                    if (s00 * s01 + s00 * s10 + s00 * s11 < 3.0) {\n                                        // intersects strip\n                                        atomic_fetch_or_explicit(&bitmap, 1 << tix, relaxed);\n                                    }\n                                }\n                            }\n                            threadgroup_barrier(mem_flags::mem_threadgroup);\n                            if (tix == 0) {\n                                rd = atomic_load_explicit(&bitmap, relaxed);\n                                atomic_store_explicit(&bitmap, 0, relaxed);\n                            }\n                            threadgroup_barrier(mem_flags::mem_threadgroup);\n                            uint polyVote = rd;\n                            while (polyVote) {\n                                uint polySubIx = ctz(polyVote);\n                                polyIx = j + polySubIx;\n                                \n                                if (hit) {\n                                    float2 start = pts[polyIx];\n                                    float2 end = pts[polyIx + 1];\n                                    float2 xymin = min(start, end);\n                                    float2 xymax = max(start, end);\n                                    if (xymax.y > y0 - hw && xymin.y < y0 + tileHeight + hw &&\n                                        xymax.x > x0 - hw && xymin.x < x0 + tileWidth + hw) {\n                                        float a = end.y - start.y;\n                                        float b = start.x - end.x;\n                                        float c = -(a * start.x + b * start.y);\n                                        float hw = 0.5 * poly.width + 0.5;\n                                        float left = a * (x0 - hw);\n                                        float right = a * (x0 + tileWidth + hw);\n                                        float top = b * (y0 - hw);\n                                        float bot = b * (y0 + tileHeight + hw);\n                                        // If all four corners are on same side of line, cull\n                                        float s00 = sign(top + left + c);\n                                        float s01 = sign(top + right + c);\n                                        float s10 = sign(bot + left + c);\n                                        float s11 = sign(bot + right + c);\n                                        if (s00 * s01 + s00 * s10 + s00 * s11 < 3.0) {\n                                            encoder.encodeLine(start, end);\n                                            anyStroke = true;\n                                        }\n                                    }\n                                } // end if (hit)\n                                \n                                polyVote &= ~(1 << polySubIx);\n                            }\n                        }\n                        if (anyStroke) {\n                            encoder.encodeStroke(poly.rgba_color, poly.width);\n                        }\n                        break;\n                    }\n                } // end switch(itemType);\n                v &= v - 1;\n            } // end while (v)\n            threadgroup_barrier(mem_flags::mem_threadgroup);\n        //} // end for (bitmapIx)\n    }\n    uint solidColor = encoder.end();\n    outTexture.write(unpack_unorm4x8_to_half(solidColor), gid);\n}\n\n// Interpret the commands in the command list to produce a pixel.\nkernel void\nrenderKernel(texture2d<half, access::write> outTexture [[texture(0)]],\n             const device char *tiles [[buffer(0)]],\n             uint2 gid [[thread_position_in_grid]],\n             uint2 tgid [[threadgroup_position_in_grid]])\n{\n    uint tileIx = tgid.y * maxTilesWidth + tgid.x;\n    const device char *src = tiles + tileIx * tileBufSize;\n    uint x = gid.x;\n    uint y = gid.y;\n    float2 xy = float2(x, y);\n\n    // Render state (maybe factor out?)\n    half3 rgb = half3(1.0);\n    float df = 1e9;\n    half signedArea = 0.0;\n\n    while (1) {\n        Cmd cmd = Cmd_read(src, 0);\n        uint tag = cmd.tag;\n        if (tag == Cmd_End) {\n            break;\n        }\n        switch (tag) {\n            case Cmd_Circle: {\n                CmdCirclePacked circle = CmdCircle_load(cmd);\n                ushort4 bbox = circle.bbox;\n                float2 xy0 = float2(bbox.x, bbox.y);\n                float2 xy1 = float2(bbox.z, bbox.w);\n                float2 center = mix(xy0, xy1, 0.5);\n                float r = length(xy - center);\n                // I should make this shade an ellipse properly but am too lazy.\n                // But see WebRender ellipse.glsl (linked in notes)\n                float circleR = min(center.x - xy0.x, center.y - xy0.y);\n                float alpha = saturate(circleR - r);\n                rgb = mix(rgb, half3(0.0), alpha);\n                break;\n            }\n            case Cmd_Line: {\n                CmdLinePacked line = CmdLine_load(cmd);\n                stroke(df, xy, line.start, line.end);\n                break;\n            }\n            case Cmd_Stroke: {\n                CmdStrokePacked stroke = CmdStroke_load(cmd);\n                half alpha = renderDf(df, stroke.halfWidth);\n                half4 fg = unpack_unorm4x8_srgb_to_half(stroke.rgba_color);\n                rgb = mix(rgb, fg.rgb, fg.a * alpha);\n                df = 1e9;\n                break;\n            }\n            case Cmd_Fill: {\n                CmdFillPacked fill = CmdFill_load(cmd);\n                float2 start = fill.start - xy;\n                float2 end = fill.end - xy;\n                float2 window = saturate(float2(start.y, end.y));\n                // maybe should be an epsilon test for better numerical stability\n                if (window.x != window.y) {\n                    float2 t = (window - start.y) / (end.y - start.y);\n                    float2 xs = mix(float2(start.x), float2(end.x), t);\n                    // This fudge factor might be inadequate when xmax is large, could\n                    // happen with small slopes.\n                    float xmin = min(min(xs.x, xs.y), 1.0) - 1e-6;\n                    float xmax = max(xs.x, xs.y);\n                    float b = min(xmax, 1.0);\n                    float c = max(b, 0.0);\n                    float d = max(xmin, 0.0);\n                    float area = (b + 0.5 * (d * d - c * c) - xmin) / (xmax - xmin);\n                    // TODO: evaluate accuracy loss from more use of half\n                    signedArea += half(area * (window.x - window.y));\n                }\n                break;\n            }\n            case Cmd_FillEdge: {\n                CmdFillEdgePacked fill = CmdFillEdge_load(cmd);\n                signedArea += fill.sign * saturate(y - fill.y + 1);\n                break;\n            }\n            case Cmd_DrawFill: {\n                CmdDrawFillPacked draw = CmdDrawFill_load(cmd);\n                half alpha = signedArea + half(draw.backdrop);\n                alpha = min(abs(alpha), 1.0h); // nonzero winding rule\n                // even-odd is: alpha = abs(alpha - 2.0 * round(0.5 * alpha))\n                // also: abs(2 * fract(0.5 * (x - 1.0)) - 1.0)\n                half4 fg = unpack_unorm4x8_srgb_to_half(draw.rgba_color);\n                rgb = mix(rgb, fg.rgb, fg.a * alpha);\n                signedArea = 0.0;\n                break;\n            }\n            case Cmd_Solid: {\n                CmdSolidPacked solid = CmdSolid_load(cmd);\n                half4 fg = unpack_unorm4x8_srgb_to_half(solid.rgba_color);\n                rgb = mix(rgb, fg.rgb, fg.a);\n                break;\n            }\n            case Cmd_Bail:\n                return;\n            // This case shouldn't happen, but we'll keep it for debugging.\n            default:\n                outTexture.write(half4(1.0, 0.0, 1.0, 1.0), gid);\n                return;\n        }\n        src += sizeof(Cmd);\n    }\n    // Linear to sRGB conversion. Note that if we had writable sRGB textures\n    // we could let this be done in the write call.\n    rgb = select(1.055 * pow(rgb, 1/2.4) - 0.055, 12.92 * rgb, rgb < 0.0031308);\n    half4 rgba = half4(rgb, 1.0);\n    outTexture.write(rgba, gid);\n}\n"
  },
  {
    "path": "TestApp/PietRenderer.h",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n@import MetalKit;\n\n@interface PietRenderer : NSObject<MTKViewDelegate>\n\n- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView;\n\n@end\n"
  },
  {
    "path": "TestApp/PietRenderer.m",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n@import MetalKit;\n\n#import \"PietRenderer.h\"\n#import \"PietShaderTypes.h\"\n#include \"piet_metal.h\"\n\n@implementation PietRenderer {\n    id<MTLDevice> _device;\n    id<MTLComputePipelineState> _tilePipelineState;\n    id<MTLComputePipelineState> _computePipelineState;\n    id<MTLRenderPipelineState> _renderPipelineState;\n    id<MTLCommandQueue> _commandQueue;\n    id<MTLTexture> _texture;\n    id<MTLTexture> _loTexture;\n    id<MTLBuffer> _sceneBuf;\n    id<MTLBuffer> _tileBuf;\n    id<MTLBuffer> _vertexBuf;\n    vector_uint2 _viewportSize;\n}\n\n- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView {\n    self = [super init];\n    if (self) {\n        NSError *error = NULL;\n        _device = mtkView.device;\n        // Note: this is consciously not sRGB, we do the conversion before writing the texture.\n        mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;\n        id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];\n        MTLRenderPipelineDescriptor *pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];\n        id<MTLFunction> tileFunction = [defaultLibrary newFunctionWithName:@\"tileKernel\"];\n        id<MTLFunction> kernelFunction = [defaultLibrary newFunctionWithName:@\"renderKernel\"];\n        id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@\"vertexShader\"];\n        id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@\"fragmentShader\"];\n        pipelineDescriptor.vertexFunction = vertexFunction;\n        pipelineDescriptor.fragmentFunction = fragmentFunction;\n        pipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;\n\n        _tilePipelineState = [_device newComputePipelineStateWithFunction:tileFunction error:&error];\n        _computePipelineState = [_device newComputePipelineStateWithFunction:kernelFunction error:&error];\n        if (!_tilePipelineState || !_computePipelineState) {\n            NSLog(@\"Failed to create compute pipeline state, error %@\", error);\n            return nil;\n        }\n        _renderPipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineDescriptor error: &error];\n\n        _commandQueue = [_device newCommandQueue];\n        \n        NSUInteger tileBufSizeBytes = maxTilesWidth * maxTilesHeight * tileBufSize;\n        // Note: consider using managed here, worth experimenting with.\n        MTLResourceOptions sceneOptions = MTLResourceStorageModeShared | MTLResourceCPUCacheModeWriteCombined;\n        _sceneBuf = [_device newBufferWithLength:16*1024*1024 options:sceneOptions];\n        _tileBuf = [_device newBufferWithLength:tileBufSizeBytes options:MTLResourceStorageModePrivate];\n    }\n    return self;\n}\n\n- (void)drawInMTKView:(nonnull MTKView *)view {\n    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];\n    commandBuffer.label = @\"RenderCommand\";\n\n    uint nTilesX = (_viewportSize.x + tileWidth - 1) / tileWidth;\n    uint nTilesY = (_viewportSize.y + tileHeight - 1) / tileHeight;\n\n    uint nTilerGroupsX = (nTilesX + tilerGroupWidth - 1) / tilerGroupWidth;\n    uint nTilerGroupsY = (nTilesY + tilerGroupHeight - 1) / tilerGroupHeight;\n\n    // Run tile compute shader.\n    id<MTLComputeCommandEncoder> computeEncoder = [commandBuffer computeCommandEncoder];\n    [computeEncoder setComputePipelineState:_tilePipelineState];\n    [computeEncoder setTexture:_loTexture atIndex:0];\n    [computeEncoder setBuffer:_sceneBuf offset:0 atIndex:0];\n    [computeEncoder setBuffer:_tileBuf offset:0 atIndex:1];\n    MTLSize tilegroupSize = MTLSizeMake(tilerGroupWidth, tilerGroupHeight, 1);\n    MTLSize tilegroupCount = MTLSizeMake(nTilerGroupsX, nTilerGroupsY, 1);\n    [computeEncoder dispatchThreadgroups:tilegroupCount threadsPerThreadgroup:tilegroupSize];\n    [computeEncoder endEncoding];\n\n    // Run compute shader for rendering.\n    computeEncoder = [commandBuffer computeCommandEncoder];\n    [computeEncoder setComputePipelineState:_computePipelineState];\n    [computeEncoder setTexture:_texture atIndex:0];\n    [computeEncoder setBuffer:_tileBuf offset:0 atIndex:0];\n    MTLSize threadgroupSize = MTLSizeMake(tileWidth, tileHeight, 1);\n    MTLSize threadgroupCount = MTLSizeMake(nTilesX, nTilesY, 1);\n    [computeEncoder dispatchThreadgroups:threadgroupCount threadsPerThreadgroup:threadgroupSize];\n    [computeEncoder endEncoding];\n    \n    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;\n    if (renderPassDescriptor != nil) {\n        id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];\n        [renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0}];\n        [renderEncoder setRenderPipelineState:_renderPipelineState];\n        [renderEncoder setVertexBuffer:_vertexBuf offset:0 atIndex:RenderVertexInputIndexVertices];\n        [renderEncoder setVertexTexture:_loTexture atIndex:0];\n        [renderEncoder setFragmentTexture:_texture atIndex:0];\n        [renderEncoder drawPrimitives:MTLPrimitiveTypePoint vertexStart:0 vertexCount:nTilesX * nTilesY];\n        [renderEncoder endEncoding];\n        [commandBuffer presentDrawable:view.currentDrawable];\n    }\n    [commandBuffer commit];\n}\n\n- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {\n    _viewportSize.x = size.width;\n    _viewportSize.y = size.height;\n    // TODO: try not to allocate as wildly on smooth resize (maybe round up\n    // the size).\n    MTLTextureDescriptor *descriptor = [[MTLTextureDescriptor alloc] init];\n    descriptor.textureType = MTLTextureType2D;\n    descriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;\n    descriptor.width = _viewportSize.x;\n    descriptor.height = _viewportSize.y;\n    descriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;\n    _texture = [_device newTextureWithDescriptor:descriptor];\n\n    uint nTilesX = (_viewportSize.x + tileWidth - 1) / tileWidth;\n    uint nTilesY = (_viewportSize.y + tileHeight - 1) / tileHeight;\n\n    descriptor.width = nTilesX;\n    descriptor.height = nTilesY;\n    _loTexture = [_device newTextureWithDescriptor:descriptor];\n    \n    uint vertexLen = nTilesX * nTilesY * sizeof(RenderVertex);\n    MTLResourceOptions vertexOptions = MTLResourceStorageModeShared | MTLResourceCPUCacheModeWriteCombined;\n    _vertexBuf = [_device newBufferWithLength:vertexLen options:vertexOptions];\n    RenderVertex *vertices = (RenderVertex *)_vertexBuf.contents;\n    uint ix = 0;\n    float scaleX = 2.0 / _viewportSize.x;\n    float scaleY = 2.0 / _viewportSize.y;\n    for (uint y = 0; y < nTilesY; y++) {\n        for (uint x = 0; x < nTilesX; x++) {\n            RenderVertex rv;\n            uint x0 = x * tileWidth + (tileWidth / 2);\n            uint y0 = y * tileHeight + (tileHeight / 2);\n            rv.position.x = x0 * scaleX - 1.0;\n            rv.position.y = y0 * -scaleY + 1.0;\n            rv.textureCoordinate.x = x0;\n            rv.textureCoordinate.y = y0;\n            vertices[ix++] = rv;\n        }\n    }\n\n    [self initScene];\n}\n\n/*\n- (void)initCardioid {\n    float cx = 1024;\n    float cy = 768;\n    float r = 750;\n    int n = 97;\n    SceneEncoder *encoder = [[SceneEncoder alloc] initWithBuffer:_sceneBuf];\n    [encoder beginGroup: 2 * (n - 1)];\n    for (int i = 1; i < n; i++) {\n        float th0 = 2 * M_PI * i / n;\n        float th1 = 2 * M_PI * ((i * 2) % n) / n;\n        vector_float2 start = simd_make_float2(cx + r * cos(th0), cy - r * sin(th0));\n        vector_float2 end = simd_make_float2(cx + r * cos(th1), cy - r * sin(th1));\n        [encoder circle:start radius: 8];\n        [encoder line:start to:end width:2 color:0xff800000];\n    }\n    [encoder endGroup];\n}\n\n- (void)fillTest {\n    const int n = 256;\n    SceneEncoder *encoder = [[SceneEncoder alloc] initWithBuffer:_sceneBuf];\n    [encoder beginGroup: n];\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < 3; j++) {\n            uint32_t x = arc4random() % _viewportSize.x;\n            uint32_t y = arc4random() % _viewportSize.y;\n            [encoder addPt:simd_make_float2(x, y)];\n        }\n        [encoder fill:arc4random() | 0xff000000];\n    }\n    [encoder endGroup];\n}\n\n- (void)initCircles {\n    const int radius = 8;\n    const int n = 256;\n    SceneEncoder *encoder = [[SceneEncoder alloc] initWithBuffer:_sceneBuf];\n    [encoder beginGroup:n + 1];\n    for (int i = 0; i < n; i++) {\n        uint32_t x = arc4random() % _viewportSize.x;\n        uint32_t y = arc4random() % _viewportSize.y;\n        [encoder circle:simd_make_float2(x, y) radius:radius];\n    }\n    [encoder line:simd_make_float2(100, 500) to:simd_make_float2(700, 600) width:100 color:0xff800000];\n    [encoder endGroup];\n}\n\n - (void)initScene {\n    //[self initCircles];\n    //[self initCardioid];\n    [self fillTest];\n}\n*/\n\n- (void)initScene {\n    init_test_scene(_sceneBuf.contents, _sceneBuf.allocatedSize);\n}\n\n@end\n"
  },
  {
    "path": "TestApp/PietShaderTypes.h",
    "content": "//  Copyright 2019 The xi-editor authors.\n\ntypedef struct\n{\n    // This is a clip space coordinate (-1 to 1).\n    vector_float2 position;\n    // This is now an integer coordinate for reading the texture.\n    vector_float2 textureCoordinate;\n} RenderVertex;\n\ntypedef enum RenderVertexInputIndex\n{\n    RenderVertexInputIndexVertices = 0,\n} RenderVertexInputIndex;\n\n// Size in pixels of an individual tile\n#define tileWidth 16\n#define tileHeight 16\n\n// Size (in tiles) of a threadgroup for tiling\n#define tilerGroupWidth 16\n#define tilerGroupHeight 2\n\n// The number of bytes in a buffer for a single tile.\n// For prototyping, this is a hard maximum, but for production we'd want\n// a mechanism to overflow.\n#define tileBufSize 4096\n\n// For simplicity, we're going to hardcode these dimensions. For production,\n// they need to be dynamic.\n#define maxTilesWidth 256\n#define maxTilesHeight 256\n"
  },
  {
    "path": "TestApp/SceneEncoder.h",
    "content": "@interface SceneEncoder: NSObject\n\n- (nonnull instancetype)initWithBuffer:(nonnull id<MTLBuffer>)buffer;\n\n- (void)beginGroup:(uint)nItems;\n\n- (void)endGroup;\n\n- (void)circle:(vector_float2)center radius:(float)radius;\n\n- (void)line:(vector_float2)start to:(vector_float2)end width:(float) width color:(uint) rgba;\n\n- (void)addPt:(vector_float2)xy;\n\n- (void)fill:(uint)rgba;\n\n@end\n\n// The following are legacy definitions - encoding in ObjC will not be supported\n// going forward.\ntypedef struct SimpleGroup {\n    uint nItems;\n    // Offset in bytes to items\n    uint itemsIx;\n    vector_ushort4 bbox[1];\n} SimpleGroup;\n\ntypedef struct PietCircle {\n    uint itemType;\n} PietCircle;\n\n// A single line to be stroked, with default parameters\ntypedef struct PietStrokeLine {\n    uint itemType;\n    uint flags; // reserved, partially for alignment\n    uint rgbaColor;\n    float width;\n    vector_float2 start;\n    vector_float2 end;\n} PietStrokeLine;\n\ntypedef struct PietFill {\n    uint itemType;\n    uint flags; // will be used for winding number rule\n    uint rgbaColor;\n    uint nPoints;\n    uint pointsIx;\n} PietFill;\n\ntypedef struct PietStrokePolyLine {\n    uint itemType;\n    uint rgbaColor;\n    float width;\n    uint nPoints;\n    uint pointsIx;\n} PietStrokePolyLine;\n\ntypedef union PietItem {\n    uint itemType;\n    PietCircle circle;\n    PietStrokeLine line;\n    PietFill fill;\n    PietStrokePolyLine poly;\n} PietItem;\n\n// This should be an enum but the storage needs to be of fixed size\n#define PIET_ITEM_CIRCLE 1\n#define PIET_ITEM_LINE 2\n#define PIET_ITEM_FILL 3\n#define PIET_ITEM_STROKE_POLYLINE 4\n"
  },
  {
    "path": "TestApp/SceneEncoder.m",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n@import MetalKit;\n\n#import \"PietShaderTypes.h\"\n#import \"SceneEncoder.h\"\n\n@implementation SceneEncoder {\n    char *_buf;\n    uint _bboxIx;\n    uint _ix;\n    uint _count;\n    // Index of beginning of free space (currently allocation is just a bump).\n    uint _freeSpace;\n    uint _pointCount;\n    vector_float4 _bbox;\n}\n\n- (nonnull instancetype)initWithBuffer:(nonnull id<MTLBuffer>)buffer {\n    _buf = buffer.contents;\n    _ix = 0;\n    _freeSpace = 0;\n    _pointCount = 0;\n    _bbox = simd_make_float4(0.0);\n    return self;\n}\n\n- (uint)alloc:(uint)size {\n    uint ix = _freeSpace;\n    _freeSpace += size;\n    return ix;\n}\n\n- (void)beginGroup:(uint)nItems {\n    uint size = sizeof(SimpleGroup) - sizeof(vector_ushort4) + nItems * (sizeof(vector_ushort4) + sizeof(PietItem));\n    uint ix = [self alloc:size];\n    SimpleGroup *group = (SimpleGroup *)(_buf + ix);\n    // Does zero-size array work in obj-C?\n    _bboxIx = ix + sizeof(SimpleGroup) - sizeof(vector_ushort4);\n    _ix = _bboxIx + sizeof(vector_ushort4) * nItems;\n    group->nItems = nItems;\n    group->itemsIx = _ix;\n    _count = nItems;\n}\n\n- (void)endGroup {\n    if (_count != 0) {\n        NSLog(@\"Not enough items encoded in group.\");\n    }\n    /*\n    for (int i = 0; i < _freeSpace / 4; i++) {\n        NSLog(@\"%04x: %08x\", i * 4, ((uint *)_buf)[i]);\n    }\n     */\n}\n\n- (void)circle:(vector_float2)center radius:(float)radius {\n    vector_float4 bbox = simd_make_float4(center.x - radius,\n                                          center.y - radius,\n                                          center.x + radius,\n                                          center.y + radius);\n    PietCircle *circle = &[self allocItem:bbox]->circle;\n    circle->itemType = PIET_ITEM_CIRCLE;\n}\n\n// The color argument is actually ABGR, which is the native format.\n// Maybe rename?\n- (void)line:(vector_float2)start to:(vector_float2)end width:(float) width color:(uint) rgba {\n    float half = 0.5 * width;\n    vector_float4 bbox = simd_make_float4(MIN(start.x, end.x) - half,\n                                          MIN(start.y, end.y) - half,\n                                          MAX(start.x, end.x) + half,\n                                          MAX(start.y, end.y) + half);\n    PietStrokeLine *line = &[self allocItem:bbox]->line;\n    line->itemType = PIET_ITEM_LINE;\n    line->rgbaColor = rgba;\n    line->width = width; // should this be half?\n    line->start = start;\n    line->end = end;\n}\n\n// This isn't dealing with subpaths and has a crude allocation strategy.\n- (void)addPt:(vector_float2)xy {\n    uint ix = [self alloc:sizeof(vector_float2)];\n    vector_float2 *dst = (vector_float2 *)(_buf + ix);\n    *dst = xy;\n    if (_pointCount == 0) {\n        _bbox = simd_make_float4(xy, xy);\n    } else {\n        _bbox = simd_make_float4(\n                                 MIN(_bbox.x, xy.x),\n                                 MIN(_bbox.y, xy.y),\n                                 MAX(_bbox.z, xy.x),\n                                 MAX(_bbox.w, xy.y)\n        );\n    }\n    _pointCount++;\n}\n\n- (void)fill:(uint)rgba {\n    PietFill *fill = &[self allocItem:_bbox]->fill;\n    fill->itemType = PIET_ITEM_FILL;\n    fill->rgbaColor = rgba;\n    fill->nPoints = _pointCount;\n    // This is a hack, needs to be fixed if we have real allocation.\n    fill->pointsIx = _freeSpace - _pointCount * sizeof(vector_float2);\n    _pointCount = 0;\n}\n\n- (PietItem *)allocItem:(vector_float4)bbox {\n    if (_count == 0) {\n        NSLog(@\"encoder group count overflow\");\n        return nil;\n    }\n    _count -= 1;\n    vector_short4 *bboxPtr = (vector_short4 *)(_buf + _bboxIx);\n    bboxPtr->x = MAX(bbox.x, 0.0);\n    bboxPtr->y = MAX(bbox.y, 0.0);\n    bboxPtr->z = bbox.z;\n    bboxPtr->w = bbox.w;\n    _bboxIx += sizeof(vector_short4);\n    PietItem *item = (PietItem *)(_buf + _ix);\n    _ix += sizeof(PietItem);\n    return item;\n}\n\n@end\n\n"
  },
  {
    "path": "TestApp/ViewController.h",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n#import <Cocoa/Cocoa.h>\n\n@import MetalKit;\n\n@interface ViewController : NSViewController\n\n@end\n\n"
  },
  {
    "path": "TestApp/ViewController.m",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n#import \"ViewController.h\"\n#import \"PietRenderer.h\"\n\n@implementation ViewController\n{\n    MTKView *_view;\n    PietRenderer *_renderer;\n}\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n\n    _view = (MTKView *)self.view;\n    _view.device = MTLCreateSystemDefaultDevice();\n    \n    if(!_view.device)\n    {\n        NSLog(@\"Metal is not supported on this device\");\n        return;\n    }\n    \n    _renderer = [[PietRenderer alloc] initWithMetalKitView:_view];\n\n    [_renderer mtkView:_view drawableSizeWillChange:_view.drawableSize];\n\n    _view.delegate = _renderer;\n}\n\n\n- (void)setRepresentedObject:(id)representedObject {\n    [super setRepresentedObject:representedObject];\n\n    // Update the view, if already loaded.\n}\n\n\n@end\n"
  },
  {
    "path": "TestApp/main.m",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"piet_metal.h\"\n\nint main(int argc, const char * argv[]) {\n    return NSApplicationMain(argc, argv);\n}\n"
  },
  {
    "path": "TestApp/piet_metal.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>com.apple.security.app-sandbox</key>\n    <true/>\n    <key>com.apple.security.files.user-selected.read-only</key>\n    <true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "include/piet_metal.h",
    "content": "#include <stdint.h>\n\nvoid init_test_scene(uint8_t *buf, ssize_t buf_size);\n"
  },
  {
    "path": "piet-gpu-derive/Cargo.toml",
    "content": "[package]\nname = \"piet-gpu-derive\"\nversion = \"0.0.0\"\nauthors = [\"Raph Levien <raph.levien@gmail.com>\"]\ndescription = \"Proc macro derives for piet-gpu.\"\nlicense = \"MIT/Apache-2.0\"\nedition = \"2018\"\nkeywords = [\"graphics\", \"2d\"]\ncategories = [\"rendering::graphics-api\"]\n\n[lib]\nproc-macro = true\n\n[dependencies]\nsyn = {version = \"1.0.5\", features = [\"extra-traits\", \"full\"]}\nquote = \"1.0.2\"\nproc-macro2 = \"1.0.4\"\n"
  },
  {
    "path": "piet-gpu-derive/src/lib.rs",
    "content": "//! TODO: explain in detail how this works.\n//!\n//! A few notes that will be helpful. Structs are encoded differently depending\n//! on whether they appear as a variant in an enum; if so, the tag is included.\n//! This allows the alignment of the struct to take the tag into account.\n\nextern crate proc_macro;\n#[macro_use]\nextern crate quote;\n\nuse std::collections::HashSet;\nuse std::fmt::Write;\nuse std::ops::Deref;\n\nuse proc_macro::TokenStream;\nuse syn::parse_macro_input;\nuse syn::{\n    Expr, ExprLit, Fields, FieldsNamed, FieldsUnnamed, GenericArgument, ItemEnum, ItemStruct,\n    Lit, PathArguments, TypeArray, TypePath,\n};\n\n/// The target shader language. We can't make this a public type because of Rust rules.\n#[derive(Copy, Clone, PartialEq)]\nenum TargetLang {\n    Hlsl,\n    Msl,\n}\n\n/// A scalar that can be represented in a packed data structure.\n#[derive(Clone, Copy, PartialEq)]\nenum GpuScalar {\n    I8,\n    I16,\n    I32,\n    F32,\n    U8,\n    U16,\n    U32,\n    // TODO: Add F16\n}\n\n/// An algebraic datatype.\n#[derive(Clone)]\nenum GpuType {\n    Scalar(GpuScalar),\n    Vector(GpuScalar, usize),\n    /// Used mostly for the body of enum variants.\n    InlineStruct(String),\n    Ref(Box<GpuType>),\n}\n\nstruct GpuEnum {\n    name: String,\n    variants: Vec<(String, Vec<GpuType>)>,\n}\n\nenum GpuTypeDef {\n    Struct(String, Vec<(String, GpuType)>),\n    Enum(GpuEnum),\n}\n\nstruct GpuModule {\n    #[allow(unused)]\n    name: String,\n    /// Set of item names that are used as enum variants.\n    enum_variants: HashSet<String>,\n    defs: Vec<GpuTypeDef>,\n}\n\nimpl TargetLang {\n    /// The typed function argument for \"buf\"\n    fn buf_arg(self) -> &'static str {\n        match self {\n            TargetLang::Hlsl => \"ByteAddressBuffer buf\",\n            TargetLang::Msl => \"const device char *buf\",\n        }\n    }\n\n    /// An expression for loading a number of uints.\n    fn load_expr(self, offset: usize, size: usize) -> String {\n        let tail = if offset == 0 {\n            \"\".into()\n        } else {\n            format!(\" + {}\", offset)\n        };\n        let size_str = vector_size_str(size);\n        match self {\n            TargetLang::Hlsl => format!(\"buf.Load{}(ref{})\", size_str, tail),\n            TargetLang::Msl => {\n                let packed = if size == 1 { \"\" } else { \"packed_\" };\n                format!(\n                    \"*(device const {}uint{}*)(buf + ref{})\",\n                    packed, size_str, tail\n                )\n            }\n        }\n    }\n}\n\nimpl GpuScalar {\n    /// The unpacked type of the scalar value.\n    fn unpacked_type(self, target: TargetLang) -> GpuScalar {\n        match target {\n            TargetLang::Hlsl => match self {\n                GpuScalar::I8 | GpuScalar::I16 => GpuScalar::I32,\n                GpuScalar::U8 | GpuScalar::U16 => GpuScalar::U32,\n                _ => self,\n            },\n            _ => self,\n        }\n    }\n\n    fn typename(self, target: TargetLang) -> &'static str {\n        if target == TargetLang::Hlsl && self.size() < 4 {\n            panic!(\n                \"Internal logic error: trying to determine HLSL typename for {} byte value\",\n                self.size()\n            );\n        }\n        match self {\n            GpuScalar::F32 => \"float\",\n            GpuScalar::I8 => \"char\",\n            GpuScalar::I16 => \"short\",\n            GpuScalar::I32 => \"int\",\n            GpuScalar::U8 => \"uchar\",\n            GpuScalar::U16 => \"ushort\",\n            GpuScalar::U32 => \"uint\",\n        }\n    }\n\n    fn size(self) -> usize {\n        match self {\n            GpuScalar::F32 | GpuScalar::I32 | GpuScalar::U32 => 4,\n            GpuScalar::I8 | GpuScalar::U8 => 1,\n            GpuScalar::I16 | GpuScalar::U16 => 2,\n        }\n    }\n\n    /// Convert an expression with type \"uint\" into the given scalar.\n    fn cvt(self, inner: &str, target: TargetLang) -> String {\n        self.cvt_vec(inner, 1, target)\n    }\n\n    /// Convert a uint vector into the given vector\n    fn cvt_vec(self, inner: &str, size: usize, target: TargetLang) -> String {\n        let size = vector_size_str(size);\n        match (target, self) {\n            (TargetLang::Hlsl, GpuScalar::F32) => format!(\"asfloat({})\", inner),\n            (TargetLang::Hlsl, GpuScalar::I32) => format!(\"asint({})\", inner),\n            (TargetLang::Msl, GpuScalar::F32) => format!(\"as_type<float{}>({})\", size, inner),\n            (TargetLang::Msl, GpuScalar::I32) => format!(\"as_type<int{}>({})\", size, inner),\n            // TODO: need to be smarter about signed int conversion\n            _ => inner.into(),\n        }\n    }\n\n    fn from_syn(ty: &syn::Type) -> Option<Self> {\n        ty_as_single_ident(ty).and_then(|ident| match ident.as_str() {\n            \"f32\" => Some(GpuScalar::F32),\n            \"i8\" => Some(GpuScalar::I8),\n            \"i16\" => Some(GpuScalar::I16),\n            \"i32\" => Some(GpuScalar::I32),\n            \"u8\" => Some(GpuScalar::U8),\n            \"u16\" => Some(GpuScalar::U16),\n            \"u32\" => Some(GpuScalar::U32),\n            _ => None,\n        })\n    }\n}\n\nimpl std::fmt::Display for GpuScalar {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            GpuScalar::F32 => write!(f, \"F32\"),\n            GpuScalar::I8 => write!(f, \"I8\"),\n            GpuScalar::I16 => write!(f, \"I16\"),\n            GpuScalar::I32 => write!(f, \"I32\"),\n            GpuScalar::U8 => write!(f, \"U8\"),\n            GpuScalar::U16 => write!(f, \"U16\"),\n            GpuScalar::U32 => write!(f, \"U32\"),\n        }\n    }\n}\n\n/// If `c = 0`, return `\"var_name`, else `\"var_name + c\"`\nfn simplified_add(var_name: &str, c: usize) -> String {\n    if c == 0 {\n        String::from(var_name)\n    } else {\n        format!(\"{} + {}\", var_name, c)\n    }\n}\n\n/// Suffix to add to scalar type to make it into a vector.\n///\n/// For size of 1, returns empty string, though \"usize1\" is usually valid.\n/// This is so we have one name for the same type, and also so the suffix\n/// can be used for `ByteAddressBuf.Load` method names.\nfn vector_size_str(size: usize) -> &'static str {\n    match size {\n        1 => \"\",\n        2 => \"2\",\n        3 => \"3\",\n        4 => \"4\",\n        _ => panic!(\"illegal vector size {}\", size)\n    }\n}\n\n/// Return number of `uints` required to store `num_bytes` bytes.\nfn size_in_uints(num_bytes: usize) -> usize {\n    // a `uint` has a size of 4 bytes, (size_in_bytes + 4 - 1) / 4\n    (num_bytes + 3) / 4\n}\n\n// TODO: this only generates unsigned extractors, we will need signed as well.\nfn generate_value_extractor(size_in_bits: u32) -> String {\n    if size_in_bits > 31 {\n        panic!(\"nonsensical to generate an extractor for a value with bit size greater than 31\");\n    }\n    let mut extractor: String = String::new();\n\n    let mask_width: usize = 2_usize.pow(size_in_bits) - 1;\n\n    write!(\n        extractor,\n        \"inline uint extract_{}bit_value(uint bit_shift, uint package) {{\\n\",\n        size_in_bits\n    )\n    .unwrap();\n    write!(extractor, \"    uint mask = {};\\n\", mask_width).unwrap();\n    write!(\n        extractor,\n        \"{}\",\n        \"    uint result = (package >> bit_shift) & mask;\\n\\n    return result;\\n}\\n\\n\"\n    )\n    .unwrap();\n\n    extractor\n}\n\n/// A `PackedField` stores `StoredField`s\n#[derive(Clone)]\nstruct StoredField {\n    name: String,\n    ty: GpuType,\n    /// The offset of the field within the packed field, in bits.\n    offset: usize,\n}\n\n/// A `PackedStruct` has `PackedField`s\n#[derive(Clone)]\nstruct PackedField {\n    name: String,\n    /// The type of the package, as stored packed.\n    ty: Option<GpuType>,\n    stored_fields: Vec<StoredField>,\n    size: usize,\n}\n\n/// Possible results of the `pack` method on a `PackedField`.\n#[derive(PartialEq)]\nenum PackResult {\n    SuccessAndOpen,\n    SuccessAndClosed,\n    FailAndClosed,\n}\n\n#[derive(Clone)]\nstruct PackedStruct {\n    name: String,\n    packed_fields: Vec<PackedField>,\n    is_enum_variant: bool,\n}\n\nstruct SpecifiedStruct {\n    name: String,\n    fields: Vec<(String, GpuType)>,\n    packed_form: PackedStruct,\n}\n\nimpl StoredField {\n    fn generate_unpacker(\n        &self,\n        packed_struct_name: &str,\n        packed_field_name: &str,\n        target: TargetLang,\n    ) -> String {\n        let mut unpacker = String::new();\n\n        // A hack to get the base struct name\n        let stripped_name = &packed_struct_name[0..packed_struct_name.len() - 6];\n        if self.ty.is_small() {\n            match self.ty {\n                GpuType::Scalar(scalar) => {\n                    let size_in_bits = 8 * scalar.size();\n                    let hlsl_typename: String = match scalar {\n                        GpuScalar::F32 | GpuScalar::I32 | GpuScalar::U32 => {\n                            panic!(\"unexpected unpacking of 32 bit value!\")\n                        }\n                        _ => String::from(scalar.unpacked_type(target).typename(target)),\n                    };\n\n                    write!(\n                        unpacker,\n                        \"inline uint {}_unpack_{}(uint {}) {{\\n    {} result;\\n\\n\",\n                        stripped_name, self.name, packed_field_name, hlsl_typename,\n                    )\n                    .unwrap();\n\n                    write!(\n                        unpacker,\n                        \"    result = extract_{}bit_value({}, {});\\n\",\n                        size_in_bits, self.offset, packed_field_name\n                    )\n                    .unwrap();\n                }\n                GpuType::Vector(scalar, unpacked_size) => {\n                    let scalar_size_in_bits = 8 * scalar.size();\n                    let unpacked_typename = self.ty.unpacked_typename(target);\n\n                    let size_in_uints = size_in_uints(&scalar.size() * unpacked_size);\n                    write!(\n                        unpacker,\n                        \"inline {} {}_unpack_{}(uint{} {}) {{\\n    {} result;\\n\\n\",\n                        unpacked_typename,\n                        stripped_name,\n                        self.name,\n                        size_in_uints,\n                        packed_field_name,\n                        unpacked_typename,\n                    )\n                    .unwrap();\n\n                    for i in 0..unpacked_size {\n                        let subscript = if size_in_uints == 1 {\n                            \"\".into()\n                        } else {\n                            format!(\"[{}]\", (i * scalar_size_in_bits) / 32)\n                        };\n                        let extracted = scalar.cvt(\n                            &format!(\n                                \"extract_{}bit_value({}, {}{})\",\n                                scalar_size_in_bits,\n                                self.offset + (i * scalar_size_in_bits) % 32,\n                                packed_field_name,\n                                subscript\n                            ),\n                            target,\n                        );\n                        write!(unpacker, \"    result[{}] = {};\\n\", i, extracted).unwrap();\n                    }\n                }\n                _ => panic!(\n                    \"only expected small types, got: {}\",\n                    self.ty.unpacked_typename(target)\n                ),\n            }\n\n            write!(unpacker, \"{}\", \"    return result;\\n\").unwrap();\n            write!(unpacker, \"{}\", \"}\\n\\n\").unwrap();\n        }\n\n        unpacker\n    }\n}\n\nimpl PackedField {\n    fn new() -> PackedField {\n        PackedField {\n            name: String::new(),\n            ty: None,\n            size: 0,\n            stored_fields: vec![],\n        }\n    }\n\n    fn pack(\n        &mut self,\n        module: &GpuModule,\n        field_type: &GpuType,\n        field_name: &str,\n    ) -> Result<PackResult, String> {\n        if !self.is_closed() {\n            let field_size = field_type.size(module);\n\n            if field_size + self.size > 4 {\n                if self.is_empty() {\n                    self.stored_fields.push(StoredField {\n                        name: field_name.into(),\n                        ty: field_type.clone(),\n                        offset: 0,\n                    });\n                    self.close(module).unwrap();\n                    Ok(PackResult::SuccessAndClosed)\n                } else {\n                    self.close(module).unwrap();\n                    Ok(PackResult::FailAndClosed)\n                }\n            } else {\n                self.stored_fields.push(StoredField {\n                    name: String::from(field_name),\n                    ty: field_type.clone(),\n                    offset: self.size * 8,\n                });\n                self.size += field_size;\n                Ok(PackResult::SuccessAndOpen)\n            }\n        } else {\n            Err(\"cannot extend closed package\".into())\n        }\n    }\n\n    fn is_empty(&self) -> bool {\n        self.stored_fields.is_empty()\n    }\n\n    fn is_closed(&self) -> bool {\n        self.ty.is_some()\n    }\n\n    /// True when the packed and unpacked types differ.\n    fn is_packed(&self, struct_result: bool) -> bool {\n        if self.stored_fields.len() != 1 {\n            return true;\n        }\n        match self.stored_fields[0].ty {\n            GpuType::Scalar(scalar) => scalar.size() < 4,\n            GpuType::Vector(scalar, _) => scalar.size() < 4,\n            GpuType::InlineStruct(_) => struct_result,\n            _ => false,\n        }\n    }\n\n    fn close(&mut self, module: &GpuModule) -> Result<(), String> {\n        if !self.is_closed() {\n            if self.is_empty() {\n                Err(\"cannot close empty package\".into())\n            } else {\n                let stored_field_names = self\n                    .stored_fields\n                    .iter()\n                    .map(|pf| pf.name.clone())\n                    .collect::<Vec<String>>();\n                self.name = stored_field_names.join(\"_\");\n\n                if self.is_packed(false) {\n                    let summed_size = self.stored_fields.iter().map(|pf| pf.ty.size(module)).sum();\n                    let size_in_uints = size_in_uints(summed_size);\n                    if size_in_uints == 1 {\n                        self.ty = Some(GpuType::Scalar(GpuScalar::U32));\n                    } else {\n                        self.ty = Some(GpuType::Vector(GpuScalar::U32, size_in_uints));\n                    }\n                } else {\n                    self.ty = Some(self.stored_fields[0].ty.clone());\n                }\n                Ok(())\n            }\n        } else {\n            Err(\"cannot close closed package\".into())\n        }\n    }\n\n    fn generate_reader(\n        &self,\n        current_offset: usize,\n        target: TargetLang,\n    ) -> Result<String, String> {\n        if let Some(ty) = &self.ty {\n            let type_name = ty.unpacked_typename(target);\n            let packed_field_name = &self.name;\n\n            match ty {\n                GpuType::Scalar(scalar) => {\n                    let load_expr = target.load_expr(current_offset, 1);\n                    let cvt_exp = scalar.cvt(&load_expr, target);\n                    Ok(format!(\n                        \"    {} {} = {};\\n\",\n                        type_name, packed_field_name, cvt_exp,\n                    ))\n                }\n                GpuType::Vector(scalar, size) => {\n                    let size_in_uints = size_in_uints(scalar.size() * size);\n                    let load_expr = target.load_expr(current_offset, size_in_uints);\n                    let cvt_exp = scalar.cvt_vec(&load_expr, *size, target);\n                    Ok(format!(\n                        \"    {}{} {} = {};\\n\",\n                        scalar.typename(target),\n                        size,\n                        packed_field_name,\n                        cvt_exp,\n                    ))\n                }\n                GpuType::InlineStruct(isn) => Ok(format!(\n                    \"    {}Packed {} = {}_read(buf, {});\\n\",\n                    isn,\n                    packed_field_name,\n                    isn,\n                    simplified_add(\"ref\", current_offset)\n                )),\n                GpuType::Ref(inner) => {\n                    if let GpuType::InlineStruct(isn) = inner.deref() {\n                        Ok(format!(\n                            \"    {}Ref {} = {};\\n\",\n                            isn,\n                            packed_field_name,\n                            target.load_expr(current_offset, 1),\n                        ))\n                    } else {\n                        Ok(format!(\n                            \"    uint {} = {};\\n\",\n                            packed_field_name,\n                            target.load_expr(current_offset, 1),\n                        ))\n                    }\n                }\n            }\n        } else {\n            Err(\"cannot generate field reader from an open packed field\".into())\n        }\n    }\n\n    fn generate_accessor(\n        &self,\n        packed_struct_name: &str,\n        ref_type: &str,\n        reader: &str,\n        target: TargetLang,\n    ) -> Result<String, String> {\n        if let Some(ty) = &self.ty {\n            let mut field_accessor = String::new();\n\n            match ty {\n                GpuType::InlineStruct(name) => {\n                    write!(\n                        field_accessor,\n                        \"inline {}Packed {}_{}({}, {} ref) {{\\n\",\n                        name,\n                        packed_struct_name,\n                        self.name,\n                        target.buf_arg(),\n                        ref_type,\n                    )\n                    .unwrap();\n                }\n                _ => {\n                    write!(\n                        field_accessor,\n                        \"inline {} {}_{}({}, {} ref) {{\\n\",\n                        ty.unpacked_typename(target),\n                        packed_struct_name,\n                        self.name,\n                        target.buf_arg(),\n                        ref_type,\n                    )\n                    .unwrap();\n                }\n            }\n            write!(field_accessor, \"{}\", reader).unwrap();\n            write!(field_accessor, \"    return {};\\n}}\\n\\n\", self.name).unwrap();\n\n            Ok(field_accessor)\n        } else {\n            Err(\"cannot generate field accessor from open packed field\".into())\n        }\n    }\n\n    fn generate_unpackers(&self, packed_struct_name: &str, target: TargetLang) -> String {\n        let mut unpackers = String::new();\n\n        for sf in &self.stored_fields {\n            write!(\n                unpackers,\n                \"{}\",\n                sf.generate_unpacker(packed_struct_name, &self.name, target)\n            )\n            .unwrap();\n        }\n\n        unpackers\n    }\n\n    fn size(&self, module: &GpuModule) -> Result<usize, String> {\n        if let Some(ty) = &self.ty {\n            Ok(ty.size(module))\n        } else {\n            Err(\"cannot calculate size of open packed field\".into())\n        }\n    }\n}\n\nimpl PackedStruct {\n    fn new(module: &GpuModule, name: &str, fields: &Vec<(String, GpuType)>) -> PackedStruct {\n        let mut packed_fields: Vec<PackedField> = Vec::new();\n\n        let mut current_packed_field = PackedField::new();\n        for (field_name, ty) in fields {\n            match current_packed_field.pack(module, &ty, &field_name).unwrap() {\n                PackResult::SuccessAndClosed => {\n                    packed_fields.push(current_packed_field);\n                    current_packed_field = PackedField::new();\n                }\n                PackResult::FailAndClosed => {\n                    packed_fields.push(current_packed_field);\n                    current_packed_field = PackedField::new();\n                    let res = current_packed_field.pack(module, &ty, &field_name).unwrap();\n                    if res == PackResult::SuccessAndClosed {\n                        packed_fields.push(current_packed_field);\n                        current_packed_field = PackedField::new();\n                    }\n                }\n                _ => {}\n            }\n        }\n\n        if !current_packed_field.is_closed() {\n            if !current_packed_field.is_empty() {\n                current_packed_field.close(module).unwrap();\n                packed_fields.push(current_packed_field);\n            }\n        }\n\n        PackedStruct {\n            name: format!(\"{}Packed\", name),\n            packed_fields,\n            is_enum_variant: module.enum_variants.contains(name),\n        }\n    }\n\n    fn generate_functions(&self, module: &GpuModule, target: TargetLang) -> String {\n        let mut r = String::new();\n        let mut field_accessors: Vec<String> = Vec::new();\n        let mut unpackers: Vec<String> = Vec::new();\n\n        // This is something of a hack to strip the \"Packed\" off the struct name\n        let stripped_name = &self.name[0..self.name.len() - 6];\n        let ref_type = format!(\"{}Ref\", stripped_name);\n\n        write!(\n            r,\n            \"inline {} {}_read({}, {} ref) {{\\n\",\n            self.name,\n            stripped_name,\n            target.buf_arg(),\n            ref_type,\n        )\n        .unwrap();\n        write!(r, \"    {} result;\\n\\n\", self.name).unwrap();\n\n        let mut current_offset: usize = 0;\n        if self.is_enum_variant {\n            // account for tag\n            current_offset = 4;\n        }\n\n        for packed_field in &self.packed_fields {\n            let reader: String = packed_field\n                .generate_reader(current_offset, target)\n                .unwrap();\n            let field_accessor: String = packed_field\n                .generate_accessor(stripped_name, &ref_type, &reader, target)\n                .unwrap();\n\n            field_accessors.push(field_accessor);\n            if packed_field.is_packed(false) {\n                unpackers.push(packed_field.generate_unpackers(&self.name, target));\n            }\n\n            write!(r, \"{}\", reader).unwrap();\n            write!(\n                r,\n                \"    result.{} = {};\\n\\n\",\n                packed_field.name, packed_field.name\n            )\n            .unwrap();\n\n            current_offset += packed_field.size(module).unwrap();\n        }\n\n        write!(r, \"    return result;\\n}}\\n\\n\",).unwrap();\n\n        for field_accessor in field_accessors {\n            write!(r, \"{}\", field_accessor).unwrap();\n        }\n\n        for unpacker in unpackers {\n            write!(r, \"{}\", unpacker).unwrap();\n        }\n\n        r\n    }\n\n    fn generate_structure_def(&self, target: TargetLang) -> String {\n        let mut r = String::new();\n\n        // The packed struct definition (is missing variable sized arrays)\n        write!(r, \"struct {} {{\\n\", self.name).unwrap();\n        if self.is_enum_variant {\n            write!(r, \"    uint tag;\\n\").unwrap();\n        }\n\n        for packed_field in self.packed_fields.iter() {\n            match packed_field.ty.as_ref().unwrap() {\n                GpuType::InlineStruct(name) => {\n                    // a packed struct will only store the packed version of any structs\n                    write!(r, \"    {}Packed {};\\n\", name, packed_field.name)\n                }\n                _ => write!(\n                    r,\n                    \"    {} {};\\n\",\n                    packed_field\n                        .ty\n                        .as_ref()\n                        .expect(&format!(\"packed field {} has no type\", packed_field.name))\n                        .unpacked_typename(target),\n                    packed_field.name\n                ),\n            }\n            .unwrap()\n        }\n        write!(r, \"{}\", \"};\\n\\n\").unwrap();\n\n        r\n    }\n\n    fn to_shader(&self, module: &GpuModule, target: TargetLang) -> String {\n        let mut r = String::new();\n\n        write!(r, \"{}\", self.generate_structure_def(target)).unwrap();\n        write!(r, \"{}\", self.generate_functions(module, target)).unwrap();\n\n        r\n    }\n}\n\nimpl SpecifiedStruct {\n    fn new(module: &GpuModule, name: &str, fields: Vec<(String, GpuType)>) -> SpecifiedStruct {\n        let packed_form = PackedStruct::new(module, name, &fields);\n\n        SpecifiedStruct {\n            name: name.to_string(),\n            fields,\n            packed_form,\n        }\n    }\n\n    fn generate_structure_def(&self, target: TargetLang) -> String {\n        let mut r = String::new();\n\n        // The unpacked struct definition (is missing variable sized arrays)\n        write!(r, \"struct {} {{\\n\", self.name).unwrap();\n\n        for (field_name, field_type) in self.fields.iter() {\n            write!(\n                r,\n                \"    {} {};\\n\",\n                field_type.unpacked_typename(target),\n                field_name\n            )\n            .unwrap()\n        }\n        write!(r, \"{}\", \"};\\n\\n\").unwrap();\n\n        r\n    }\n\n    fn generate_unpacker(&self) -> String {\n        let mut r = String::new();\n\n        write!(\n            r,\n            \"inline {} {}_unpack({} packed_form) {{\\n\",\n            self.name, self.name, self.packed_form.name,\n        )\n        .unwrap();\n\n        write!(r, \"    {} result;\\n\\n\", self.name).unwrap();\n        for (field_name, field_type) in self.fields.iter() {\n            let packed_field = self\n                .packed_form\n                .packed_fields\n                .iter()\n                .find(|&pf| {\n                    pf.stored_fields\n                        .iter()\n                        .find(|&sf| sf.name == field_name.as_str())\n                        .is_some()\n                })\n                .expect(&format!(\n                    \"no packed field stores {} in {}Packed\",\n                    field_name, self.name\n                ));\n            if packed_field.is_packed(true) {\n                match field_type {\n                    GpuType::InlineStruct(name) => {\n                        write!(\n                            r,\n                            \"    result.{} = {}_unpack(packed_form.{});\\n\",\n                            field_name, name, packed_field.name\n                        )\n                        .unwrap();\n                    }\n                    _ => {\n                        write!(\n                            r,\n                            \"    result.{} = {}_unpack_{}(packed_form.{});\\n\",\n                            field_name, self.name, field_name, packed_field.name\n                        )\n                        .unwrap();\n                    }\n                }\n            } else {\n                write!(\n                    r,\n                    \"    result.{} = packed_form.{};\\n\",\n                    field_name, packed_field.name\n                )\n                .unwrap();\n            }\n        }\n        write!(r, \"{}\", \"\\n    return result;\\n}\\n\\n\").unwrap();\n        r\n    }\n\n    fn to_shader(&self, target: TargetLang) -> String {\n        let mut r = String::new();\n\n        write!(r, \"{}\", self.generate_structure_def(target)).unwrap();\n        write!(r, \"{}\", self.generate_unpacker()).unwrap();\n\n        r\n    }\n}\n\nimpl GpuType {\n    // The type name for the *unpacked* version of the type.\n    fn unpacked_typename(&self, target: TargetLang) -> String {\n        match self {\n            GpuType::Scalar(scalar) => scalar.unpacked_type(target).typename(target).into(),\n            GpuType::Vector(scalar, size) => match scalar {\n                GpuScalar::F32 | GpuScalar::I32 | GpuScalar::U32 => {\n                    format!(\"{}{}\", scalar.unpacked_type(target).typename(target), size)\n                }\n                _ => {\n                    if *size == 1 {\n                        \"uint\".into()\n                    } else {\n                        format!(\"uint{}\", size)\n                    }\n                }\n            },\n            GpuType::InlineStruct(name) => name.to_string(),\n            // TODO: probably want to have more friendly names for simple struct refs.\n            GpuType::Ref(inner) => {\n                if let GpuType::InlineStruct(name) = inner.deref() {\n                    format!(\"{}Ref\", name)\n                } else {\n                    \"uint\".into()\n                }\n            }\n        }\n    }\n\n    fn size(&self, module: &GpuModule) -> usize {\n        match self {\n            GpuType::Scalar(scalar) => scalar.size(),\n            GpuType::Vector(scalar, size) => scalar.size() * size,\n            GpuType::InlineStruct(name) => module.resolve_by_name(&name).unwrap().size(module),\n            GpuType::Ref(_name) => 4,\n        }\n    }\n\n    fn alignment(&self, module: &GpuModule) -> usize {\n        // TODO: there are alignment problems with vectors of 3\n        match self {\n            GpuType::Scalar(scalar) => scalar.size(),\n            GpuType::Vector(scalar, size) => scalar.size() * size,\n            GpuType::InlineStruct(name) => module.resolve_by_name(&name).unwrap().alignment(module),\n            GpuType::Ref(_name) => 4,\n        }\n    }\n\n    /// Report whether type is a scalar or simple vector\n    fn is_small(&self) -> bool {\n        match self {\n            GpuType::Scalar(_) => true,\n            GpuType::Vector(_, _) => true,\n            GpuType::InlineStruct(_) => false,\n            GpuType::Ref(_) => true,\n        }\n    }\n\n    fn from_syn(ty: &syn::Type) -> Result<Self, String> {\n        //println!(\"gputype {:#?}\", ty);\n        if let Some(scalar) = GpuScalar::from_syn(ty) {\n            return Ok(GpuType::Scalar(scalar));\n        }\n        if let Some(name) = ty_as_single_ident(ty) {\n            // Note: we're not doing any validation here.\n            return Ok(GpuType::InlineStruct(name));\n        }\n        match ty {\n            syn::Type::Path(TypePath {\n                path: syn::Path { segments, .. },\n                ..\n            }) => {\n                if segments.len() == 1 {\n                    let seg = &segments[0];\n                    if seg.ident == \"Ref\" {\n                        if let PathArguments::AngleBracketed(args) = &seg.arguments {\n                            if args.args.len() == 1 {\n                                if let GenericArgument::Type(inner) = &args.args[0] {\n                                    let inner_ty = GpuType::from_syn(inner)?;\n                                    return Ok(GpuType::Ref(Box::new(inner_ty)));\n                                }\n                            }\n                        }\n                    }\n                }\n                Err(\"unknown path case\".into())\n            }\n            syn::Type::Array(TypeArray { elem, len, .. }) => {\n                if let Some(elem) = GpuScalar::from_syn(&elem) {\n                    if let Some(len) = expr_int_lit(len) {\n                        // maybe sanity-check length here\n                        Ok(GpuType::Vector(elem, len))\n                    } else {\n                        Err(\"can't deal with variable length scalar arrays\".into())\n                    }\n                } else {\n                    Err(\"can't deal with non-scalar arrays\".into())\n                }\n            }\n            _ => Err(\"unknown type\".into()),\n        }\n    }\n}\n\nimpl GpuTypeDef {\n    fn from_syn(item: &syn::Item) -> Result<Self, String> {\n        match item {\n            syn::Item::Struct(ItemStruct {\n                ident,\n                fields: Fields::Named(FieldsNamed { named, .. }),\n                ..\n            }) => {\n                let mut fields = Vec::new();\n                for field in named {\n                    let field_ty = GpuType::from_syn(&field.ty)?;\n                    let field_name = field.ident.as_ref().ok_or(\"need name\".to_string())?;\n                    fields.push((field_name.to_string(), field_ty));\n                }\n                Ok(GpuTypeDef::Struct(ident.to_string(), fields))\n            }\n            syn::Item::Enum(ItemEnum {\n                ident, variants, ..\n            }) => {\n                let mut v = Vec::new();\n                for variant in variants {\n                    let vname = variant.ident.to_string();\n                    let mut fields = Vec::new();\n                    if let Fields::Unnamed(FieldsUnnamed { unnamed, .. }) = &variant.fields {\n                        for field in unnamed {\n                            fields.push(GpuType::from_syn(&field.ty)?);\n                        }\n                    }\n                    v.push((vname, fields));\n                }\n                let en = GpuEnum {\n                    name: ident.to_string(),\n                    variants: v,\n                };\n                Ok(GpuTypeDef::Enum(en))\n            }\n            _ => {\n                eprintln!(\"{:#?}\", item);\n                Err(\"unknown item\".into())\n            }\n        }\n    }\n\n    fn name(&self) -> &str {\n        match self {\n            GpuTypeDef::Struct(name, _) => &name,\n            GpuTypeDef::Enum(en) => &en.name,\n        }\n    }\n\n    fn collect_refs(&self, enum_variants: &mut HashSet<String>) {\n        if let GpuTypeDef::Enum(en) = self {\n            for variant in &en.variants {\n                if let Some(GpuType::InlineStruct(name)) = variant.1.first() {\n                    enum_variants.insert(name.clone());\n                }\n            }\n        }\n    }\n\n    /// Size of the body of the definition.\n    fn size(&self, module: &GpuModule) -> usize {\n        match self {\n            GpuTypeDef::Struct(name, fields) => {\n                let mut offset = 0;\n                if module.enum_variants.contains(name) {\n                    offset += 4;\n                }\n                for (_name, field) in fields {\n                    offset += align_padding(offset, field.alignment(module));\n                    offset += field.size(module);\n                }\n                offset\n            }\n            GpuTypeDef::Enum(en) => {\n                let mut max_offset = 4;\n                for (_name, fields) in &en.variants {\n                    let mut offset = 4;\n                    for field in fields {\n                        if let GpuType::InlineStruct(_) = field {\n                            if offset == 4 {\n                                offset = 0;\n                            }\n                        }\n                        // Alignment needs work :/\n                        //offset += align_padding(offset, field.alignment(module));\n                        offset += field.size(module);\n                    }\n                    max_offset = max_offset.max(offset);\n                }\n                max_offset\n            }\n        }\n    }\n\n    /// Alignment of the body of the definition.\n    fn alignment(&self, module: &GpuModule) -> usize {\n        match self {\n            GpuTypeDef::Struct(name, fields) => {\n                let mut alignment = 1;\n                if module.enum_variants.contains(name) {\n                    alignment = 4;\n                }\n                for (_name, field) in fields {\n                    alignment = alignment.max(field.alignment(module));\n                }\n                alignment\n            }\n            GpuTypeDef::Enum(_en) => unimplemented!(),\n        }\n    }\n\n    // TODO: implement this in new scheme\n    /*\n    fn to_metal_load_enum(&self, enum_name: &str, module: &GpuModule, r: &mut String) {\n        match self {\n            GpuTypeDef::Struct(name, fields) => {\n                write!(\n                    r,\n                    \"{}Packed {}_load(const thread {} &s) {{\\n\",\n                    name, name, enum_name\n                )\n                .unwrap();\n                write!(r, \"    {}Packed r;\\n\", name).unwrap();\n                write!(r, \"    r.tag = s.tag;\\n\").unwrap();\n                let mut offset = 4;\n                for (fieldname, ty) in fields {\n                    offset += align_padding(offset, ty.alignment(module));\n                    let mty = ty.metal_typename();\n                    // maybe better to load from `body` array rather than pointer foo\n                    write!(\n                        r,\n                        \"    r.{} = *((const thread {} *)((const thread char *)&s + {}));\\n\",\n                        fieldname, mty, offset\n                    )\n                    .unwrap();\n                    offset += ty.size(module);\n                }\n                write!(r, \"    return r;\\n\").unwrap();\n                write!(r, \"}}\\n\").unwrap();\n            }\n            _ => panic!(\"internal inconsistency\"),\n        }\n    }\n    */\n\n    // TODO: implement writers\n    /*\n    fn to_metal_wr(&self, _module: &GpuModule) -> String {\n        let mut r = String::new();\n        match self {\n            GpuTypeDef::Struct(name, _fields) => {\n                // Write of packed structure\n                write!(\n                    r,\n                    \"void {}_write(device char *buf, {}Ref ref, {}Packed s) {{\\n\",\n                    name, name, name\n                )\n                .unwrap();\n                write!(r, \"    *((device {}Packed *)(buf + ref)) = s;\\n\", name).unwrap();\n                write!(r, \"}}\\n\").unwrap();\n            }\n            // We don't write individual enum structs, we only write their variants.\n            GpuTypeDef::Enum(en) => {\n                if en.variants.iter().any(|(_name, fields)| fields.is_empty()) {\n                    write!(\n                        r,\n                        \"void {}_write_tag(device char *buf, CmdRef ref, uint tag) {{\\n\",\n                        en.name\n                    )\n                    .unwrap();\n                    write!(r, \"    ((device {} *)(buf + ref))->tag = tag;\\n\", en.name).unwrap();\n                    write!(r, \"}}\\n\").unwrap();\n                }\n            }\n        }\n        r\n    }\n    */\n\n    fn to_shader(&self, module: &GpuModule, target: TargetLang) -> String {\n        let mut r = String::new();\n\n        match self {\n            GpuTypeDef::Struct(name, fields) => {\n                let structure = SpecifiedStruct::new(module, name, fields.clone());\n                write!(r, \"{}\", structure.packed_form.to_shader(module, target)).unwrap();\n                write!(r, \"{}\", structure.to_shader(target)).unwrap();\n            }\n            GpuTypeDef::Enum(en) => {\n                let rn = format!(\"{}Ref\", en.name);\n\n                write!(r, \"struct {} {{\\n\", en.name).unwrap();\n                write!(r, \"    uint tag;\\n\").unwrap();\n\n                let size = self.size(module);\n                // TODO: this sometimes predicts incorrect number of u32s needed to store body (differences with metal alignment)\n                let body_size = ((size + 3) >> 2) - 1;\n\n                write!(r, \"    uint body[{}];\\n\", body_size).unwrap();\n                write!(r, \"}};\\n\").unwrap();\n                write!(\n                    r,\n                    \"inline uint {}_tag({}, {} ref) {{\\n\",\n                    en.name,\n                    target.buf_arg(),\n                    rn\n                )\n                .unwrap();\n\n                write!(\n                    r,\n                    \"    uint result = {};\\n    return result;\\n\",\n                    target.load_expr(0, 1)\n                )\n                .unwrap();\n                write!(r, \"}}\\n\\n\").unwrap();\n\n                if target == TargetLang::Hlsl {\n                    let quotient_in_u32x4 = size / (4 * GpuScalar::U32.size());\n                    let remainder_in_u32s = (size / 4) % 4;\n                    write!(r, \"inline void {}_copy(ByteAddressBuffer src, uint src_ref, RWByteAddressBuffer dst, uint dst_ref) {{\\n\", en.name).unwrap();\n                    for i in 0..quotient_in_u32x4 {\n                        write!(\n                            r,\n                            \"    uint4 group{} = src.Load4({});\\n\",\n                            i,\n                            simplified_add(\"src_ref\", i * 4 * 4)\n                        )\n                        .unwrap();\n                        write!(\n                            r,\n                            \"    dst.Store4({}, group{});\\n\",\n                            simplified_add(\"dst_ref\", i * 4 * 4),\n                            i,\n                        )\n                        .unwrap();\n                    }\n                    if remainder_in_u32s > 0 {\n                        let tail = vector_size_str(remainder_in_u32s);\n                        write!(\n                            r,\n                            \"\\n    uint{} group{} = src.Load{}({});\\n\",\n                            tail,\n                            quotient_in_u32x4,\n                            tail,\n                            simplified_add(\"src_ref\", quotient_in_u32x4 * 4 * 4)\n                        )\n                        .unwrap();\n                        write!(\n                            r,\n                            \"    dst.Store{}({}, group{});\\n\",\n                            tail,\n                            simplified_add(\"dst_ref\", quotient_in_u32x4 * 4 * 4),\n                            quotient_in_u32x4\n                        )\n                        .unwrap();\n                    }\n                    write!(r, \"{}\", \"}\\n\\n\").unwrap();\n                }\n            }\n        }\n        r\n    }\n}\n\nimpl GpuModule {\n    fn from_syn(module: &syn::ItemMod) -> Result<Self, String> {\n        let name = module.ident.to_string();\n        let mut defs = Vec::new();\n        let mut enum_variants = HashSet::new();\n        if let Some((_brace, items)) = &module.content {\n            for item in items {\n                let def = GpuTypeDef::from_syn(item)?;\n                def.collect_refs(&mut enum_variants);\n                defs.push(def);\n            }\n        }\n        Ok(GpuModule {\n            name,\n            enum_variants,\n            defs,\n        })\n    }\n\n    fn resolve_by_name(&self, name: &str) -> Result<&GpuTypeDef, String> {\n        for def in &self.defs {\n            if def.name() == name {\n                return Ok(&def);\n            }\n        }\n        Err(format!(\"could not find {} in module\", name))\n    }\n\n    fn to_shader(&self, target: TargetLang) -> String {\n        let mut r = String::new();\n\n        write!(&mut r, \"{}\", generate_value_extractor(8)).unwrap();\n        write!(&mut r, \"{}\", generate_value_extractor(16)).unwrap();\n\n        for def in &self.defs {\n            match def {\n                GpuTypeDef::Struct(name, _) => {\n                    write!(&mut r, \"typedef uint {}Ref;\\n\", name).unwrap();\n                }\n                GpuTypeDef::Enum(_) => {\n                    write!(&mut r, \"typedef uint {}Ref;\\n\", def.name()).unwrap();\n                }\n            }\n        }\n\n        write!(&mut r, \"\\n\").unwrap();\n        for def in &self.defs {\n            r.push_str(&def.to_shader(self, target));\n        }\n\n        for def in &self.defs {\n            let name = def.name();\n            if !(self.enum_variants.contains(name)) {\n                write!(\n                    r,\n                    \"#define {}_SIZE {}\\n\",\n                    to_snake_case(name).to_uppercase(),\n                    def.size(self)\n                )\n                .unwrap();\n            }\n            if let GpuTypeDef::Enum(en) = def {\n                let mut tag: usize = 0;\n                for (name, _fields) in &en.variants {\n                    write!(r, \"#define {}_{} {}\\n\", en.name, name, tag).unwrap();\n                    tag += 1;\n                }\n            }\n        }\n        r\n    }\n}\n\nfn ty_as_single_ident(ty: &syn::Type) -> Option<String> {\n    if let syn::Type::Path(TypePath {\n        path: syn::Path { segments, .. },\n        ..\n    }) = ty\n    {\n        if segments.len() == 1 {\n            let seg = &segments[0];\n            if seg.arguments == PathArguments::None {\n                return Some(seg.ident.to_string());\n            }\n        }\n    }\n    None\n}\n\nfn expr_int_lit(e: &Expr) -> Option<usize> {\n    if let Expr::Lit(ExprLit {\n        lit: Lit::Int(lit_int),\n        ..\n    }) = e\n    {\n        lit_int.base10_parse().ok()\n    } else {\n        None\n    }\n}\n\nfn align_padding(offset: usize, alignment: usize) -> usize {\n    offset.wrapping_neg() & (alignment - 1)\n}\n\n/* TODO: make derive macros\n#[proc_macro_derive(PietMetal)]\npub fn derive_piet_metal(input: TokenStream) -> TokenStream {\n    let input = parse_macro_input!(input as syn::DeriveInput);\n    derive_proc_metal_impl(input)\n        .unwrap_or_else(|err| err.to_compile_error())\n        .into()\n}\n\nfn derive_proc_metal_impl(input: syn::DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> {\n    println!(\"input: {:#?}\", input);\n    match &input.data {\n        Data::Struct { .. } => {\n            println!(\"it's a struct!\");\n        }\n        _ => (),\n    }\n    let s = \"this is a string\";\n    let expanded = quote! {\n        fn foo() {\n            println!(\"this was generated by proc macro: {}\", #s);\n        }\n    };\n    Ok(expanded)\n}\n*/\n\n#[proc_macro]\npub fn piet_gpu(input: TokenStream) -> TokenStream {\n    let input = parse_macro_input!(input as syn::ItemMod);\n    //println!(\"input: {:#?}\", input);\n    let module = GpuModule::from_syn(&input).unwrap();\n    let gen_gpu_fn = format_ident!(\"gen_gpu_{}\", input.ident);\n    let hlsl_result = module.to_shader(TargetLang::Hlsl);\n    let msl_result = module.to_shader(TargetLang::Msl);\n    let expanded = quote! {\n        fn #gen_gpu_fn(lang: &str) -> String {\n            match lang {\n                \"HLSL\" => #hlsl_result.into(),\n                \"MSL\" => #msl_result.into(),\n                _ => panic!(\"unkonwn shader lang {}\", lang),\n            }\n        }\n    };\n    expanded.into()\n}\n\nfn to_snake_case(mut str: &str) -> String {\n    let mut words = vec![];\n    // Preserve leading underscores\n    str = str.trim_start_matches(|c: char| {\n        if c == '_' {\n            words.push(String::new());\n            true\n        } else {\n            false\n        }\n    });\n    for s in str.split('_') {\n        let mut last_upper = false;\n        let mut buf = String::new();\n        if s.is_empty() {\n            continue;\n        }\n        for ch in s.chars() {\n            if !buf.is_empty() && buf != \"'\" && ch.is_uppercase() && !last_upper {\n                words.push(buf);\n                buf = String::new();\n            }\n            last_upper = ch.is_uppercase();\n            buf.extend(ch.to_lowercase());\n        }\n        words.push(buf);\n    }\n    words.join(\"_\")\n}\n"
  },
  {
    "path": "piet-metal.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\tAE20EE122277739F00207011 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AE20EE112277739F00207011 /* AppDelegate.m */; };\n\t\tAE20EE152277739F00207011 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AE20EE142277739F00207011 /* ViewController.m */; };\n\t\tAE20EE17227773A000207011 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE20EE16227773A000207011 /* Assets.xcassets */; };\n\t\tAE20EE1A227773A000207011 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE20EE18227773A000207011 /* Main.storyboard */; };\n\t\tAE20EE1D227773A000207011 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AE20EE1C227773A000207011 /* main.m */; };\n\t\tAE95B97A227CBE5A0038195A /* piet_metal.h in Headers */ = {isa = PBXBuildFile; fileRef = AE95B979227CBE590038195A /* piet_metal.h */; };\n\t\tAE95B981227CC0020038195A /* libpiet_metal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AE95B975227CBDCD0038195A /* libpiet_metal.a */; };\n\t\tAE99FF612277800100937601 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = AE99FF5E2277800100937601 /* README.md */; };\n\t\tAE99FF652277809B00937601 /* PietRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = AE99FF642277809B00937601 /* PietRenderer.m */; };\n\t\tAE99FF692277CE7200937601 /* PietRender.metal in Sources */ = {isa = PBXBuildFile; fileRef = AE99FF682277CE7200937601 /* PietRender.metal */; };\n\t\tAEA81C9C227A698E0051DAD5 /* SceneEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = AEA81C9B227A698E0051DAD5 /* SceneEncoder.m */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\tAE95B97F227CBFEA0038195A /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = AE20EE052277739F00207011 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = AE95B974227CBDCD0038195A;\n\t\t\tremoteInfo = piet_metal;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXFileReference section */\n\t\tAE20EE0D2277739F00207011 /* piet-metal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = \"piet-metal.app\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tAE20EE102277739F00207011 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = \"<group>\"; };\n\t\tAE20EE112277739F00207011 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = \"<group>\"; };\n\t\tAE20EE132277739F00207011 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = \"<group>\"; };\n\t\tAE20EE142277739F00207011 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = \"<group>\"; };\n\t\tAE20EE16227773A000207011 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\tAE20EE19227773A000207011 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = \"<group>\"; };\n\t\tAE20EE1B227773A000207011 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tAE20EE1C227773A000207011 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = \"<group>\"; };\n\t\tAE20EE1E227773A000207011 /* piet_metal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = piet_metal.entitlements; sourceTree = \"<group>\"; };\n\t\tAE340C0923C546BF00F9D470 /* GenTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GenTypes.h; sourceTree = \"<group>\"; };\n\t\tAE95B96F227CBC1B0038195A /* libpiet_metal.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpiet_metal.a; path = target/debug/libpiet_metal.a; sourceTree = \"<group>\"; };\n\t\tAE95B975227CBDCD0038195A /* libpiet_metal.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libpiet_metal.a; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tAE95B979227CBE590038195A /* piet_metal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = piet_metal.h; path = include/piet_metal.h; sourceTree = \"<group>\"; };\n\t\tAE99FF5E2277800100937601 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = \"<group>\"; };\n\t\tAE99FF642277809B00937601 /* PietRenderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PietRenderer.m; sourceTree = \"<group>\"; };\n\t\tAE99FF66227780B000937601 /* PietRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PietRenderer.h; sourceTree = \"<group>\"; };\n\t\tAE99FF672277CDF200937601 /* PietShaderTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PietShaderTypes.h; sourceTree = \"<group>\"; };\n\t\tAE99FF682277CE7200937601 /* PietRender.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = PietRender.metal; sourceTree = \"<group>\"; };\n\t\tAEA81C9B227A698E0051DAD5 /* SceneEncoder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneEncoder.m; sourceTree = \"<group>\"; };\n\t\tAEA81C9D227A69B50051DAD5 /* SceneEncoder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneEncoder.h; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\tAE20EE0A2277739F00207011 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tAE95B981227CC0020038195A /* libpiet_metal.a in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\tAE20EE042277739F00207011 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tAE95B979227CBE590038195A /* piet_metal.h */,\n\t\t\t\tAE99FF5E2277800100937601 /* README.md */,\n\t\t\t\tAE20EE0F2277739F00207011 /* TestApp */,\n\t\t\t\tAE20EE0E2277739F00207011 /* Products */,\n\t\t\t\tAE95B96E227CBC1A0038195A /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tAE20EE0E2277739F00207011 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tAE20EE0D2277739F00207011 /* piet-metal.app */,\n\t\t\t\tAE95B975227CBDCD0038195A /* libpiet_metal.a */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tAE20EE0F2277739F00207011 /* TestApp */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tAE20EE102277739F00207011 /* AppDelegate.h */,\n\t\t\t\tAE20EE112277739F00207011 /* AppDelegate.m */,\n\t\t\t\tAE20EE16227773A000207011 /* Assets.xcassets */,\n\t\t\t\tAE20EE1B227773A000207011 /* Info.plist */,\n\t\t\t\tAE20EE1C227773A000207011 /* main.m */,\n\t\t\t\tAE20EE18227773A000207011 /* Main.storyboard */,\n\t\t\t\tAE20EE1E227773A000207011 /* piet_metal.entitlements */,\n\t\t\t\tAE99FF682277CE7200937601 /* PietRender.metal */,\n\t\t\t\tAE99FF66227780B000937601 /* PietRenderer.h */,\n\t\t\t\tAE99FF642277809B00937601 /* PietRenderer.m */,\n\t\t\t\tAE99FF672277CDF200937601 /* PietShaderTypes.h */,\n\t\t\t\tAEA81C9D227A69B50051DAD5 /* SceneEncoder.h */,\n\t\t\t\tAEA81C9B227A698E0051DAD5 /* SceneEncoder.m */,\n\t\t\t\tAE20EE132277739F00207011 /* ViewController.h */,\n\t\t\t\tAE20EE142277739F00207011 /* ViewController.m */,\n\t\t\t\tAE340C0923C546BF00F9D470 /* GenTypes.h */,\n\t\t\t);\n\t\t\tpath = TestApp;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tAE95B96E227CBC1A0038195A /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tAE95B96F227CBC1B0038195A /* libpiet_metal.a */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXHeadersBuildPhase section */\n\t\tAE95B971227CBDCD0038195A /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tAE95B97A227CBE5A0038195A /* piet_metal.h in Headers */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXHeadersBuildPhase section */\n\n/* Begin PBXNativeTarget section */\n\t\tAE20EE0C2277739F00207011 /* piet-metal */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = AE20EE21227773A000207011 /* Build configuration list for PBXNativeTarget \"piet-metal\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tAE20EE092277739F00207011 /* Sources */,\n\t\t\t\tAE20EE0A2277739F00207011 /* Frameworks */,\n\t\t\t\tAE20EE0B2277739F00207011 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tAE95B980227CBFEA0038195A /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = \"piet-metal\";\n\t\t\tproductName = \"piet-metal\";\n\t\t\tproductReference = AE20EE0D2277739F00207011 /* piet-metal.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n\t\tAE95B974227CBDCD0038195A /* piet_metal */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = AE95B976227CBDCD0038195A /* Build configuration list for PBXNativeTarget \"piet_metal\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tAE95B971227CBDCD0038195A /* Headers */,\n\t\t\t\tAE95B97B227CBE660038195A /* ShellScript */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = piet_metal;\n\t\t\tproductName = piet_metal;\n\t\t\tproductReference = AE95B975227CBDCD0038195A /* libpiet_metal.a */;\n\t\t\tproductType = \"com.apple.product-type.library.static\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tAE20EE052277739F00207011 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1020;\n\t\t\t\tORGANIZATIONNAME = \"Raph Levien\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\tAE20EE0C2277739F00207011 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 10.2.1;\n\t\t\t\t\t};\n\t\t\t\t\tAE95B974227CBDCD0038195A = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 10.2.1;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = AE20EE082277739F00207011 /* Build configuration list for PBXProject \"piet-metal\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = AE20EE042277739F00207011;\n\t\t\tproductRefGroup = AE20EE0E2277739F00207011 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tAE20EE0C2277739F00207011 /* piet-metal */,\n\t\t\t\tAE95B974227CBDCD0038195A /* piet_metal */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tAE20EE0B2277739F00207011 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tAE99FF612277800100937601 /* README.md in Resources */,\n\t\t\t\tAE20EE17227773A000207011 /* Assets.xcassets in Resources */,\n\t\t\t\tAE20EE1A227773A000207011 /* Main.storyboard in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\tAE95B97B227CBE660038195A /* ShellScript */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"# When building from Xcode we want to ensure that `cargo` is in PATH.\\n# as a convenience, add the default cargo install location\\nexport PATH=\\\"$PATH:${HOME}/.cargo/bin\\\"\\n\\nset -e\\n\\nif [[ $CONFIGURATION = \\\"Debug\\\" ]]; then\\nRUST_CONFIGURATION=\\\"debug\\\"\\nRUST_CONFIGURATION_FLAG=\\\"\\\"\\nelse\\nRUST_CONFIGURATION=\\\"release\\\"\\nRUST_CONFIGURATION_FLAG=\\\"--release\\\"\\nfi\\n\\ncd \\\"${SRCROOT}\\\"\\n\\necho \\\"rust config ${RUST_CONFIGURATION}, action ${ACTION}\\\"\\n\\nif [[ ${ACTION:-build} = \\\"build\\\" ]]; then\\ncargo build $RUST_CONFIGURATION_FLAG\\ncp \\\"${SRCROOT}/target/${RUST_CONFIGURATION}/libpiet_metal.a\\\" \\\"${BUILT_PRODUCTS_DIR}/\\\"\\n#cp \\\"${SRCROOT}/include/piet_metal.h\\\" \\\"${BUILT_PRODUCTS_DIR}/\\\"\\nelif [[ $ACTION = \\\"clean\\\" ]]; then\\necho \\\"cleaning\\\"\\ncargo clean\\nfi\\n\\nset +e\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\tAE20EE092277739F00207011 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tAE20EE152277739F00207011 /* ViewController.m in Sources */,\n\t\t\t\tAE20EE1D227773A000207011 /* main.m in Sources */,\n\t\t\t\tAE99FF652277809B00937601 /* PietRenderer.m in Sources */,\n\t\t\t\tAEA81C9C227A698E0051DAD5 /* SceneEncoder.m in Sources */,\n\t\t\t\tAE99FF692277CE7200937601 /* PietRender.metal in Sources */,\n\t\t\t\tAE20EE122277739F00207011 /* AppDelegate.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\tAE95B980227CBFEA0038195A /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = AE95B974227CBDCD0038195A /* piet_metal */;\n\t\t\ttargetProxy = AE95B97F227CBFEA0038195A /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin PBXVariantGroup section */\n\t\tAE20EE18227773A000207011 /* Main.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\tAE20EE19227773A000207011 /* Base */,\n\t\t\t);\n\t\t\tname = Main.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\tAE20EE1F227773A000207011 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.14;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tAE20EE20227773A000207011 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.14;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tAE20EE22227773A000207011 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = TestApp/piet_metal.entitlements;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tINFOPLIST_FILE = TestApp/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/target/debug\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.levien.piet-metal\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tAE20EE23227773A000207011 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = TestApp/piet_metal.entitlements;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tINFOPLIST_FILE = TestApp/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/target/debug\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.levien.piet-metal\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tAE95B977227CBDCD0038195A /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tEXECUTABLE_PREFIX = lib;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tAE95B978227CBDCD0038195A /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tEXECUTABLE_PREFIX = lib;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\tAE20EE082277739F00207011 /* Build configuration list for PBXProject \"piet-metal\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tAE20EE1F227773A000207011 /* Debug */,\n\t\t\t\tAE20EE20227773A000207011 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tAE20EE21227773A000207011 /* Build configuration list for PBXNativeTarget \"piet-metal\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tAE20EE22227773A000207011 /* Debug */,\n\t\t\t\tAE20EE23227773A000207011 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tAE95B976227CBDCD0038195A /* Build configuration list for PBXNativeTarget \"piet_metal\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tAE95B977227CBDCD0038195A /* Debug */,\n\t\t\t\tAE95B978227CBDCD0038195A /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = AE20EE052277739F00207011 /* Project object */;\n}\n"
  },
  {
    "path": "piet-metal.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:piet-metal.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "piet-metal.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "piet-metal.xcodeproj/xcuserdata/raph.xcuserdatad/xcschemes/xcschememanagement.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>SchemeUserState</key>\n\t<dict>\n\t\t<key>libpiet_metal.xcscheme_^#shared#^_</key>\n\t\t<dict>\n\t\t\t<key>orderHint</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<key>libpietmetal.xcscheme_^#shared#^_</key>\n\t\t<dict>\n\t\t\t<key>orderHint</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<key>piet-metal.xcscheme_^#shared#^_</key>\n\t\t<dict>\n\t\t\t<key>orderHint</key>\n\t\t\t<integer>0</integer>\n\t\t</dict>\n\t\t<key>piet_metal.xcscheme_^#shared#^_</key>\n\t\t<dict>\n\t\t\t<key>orderHint</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "src/flatten.rs",
    "content": "//  Copyright 2019 The xi-editor authors.\n\n//! Quick and dirty path flattening.\n\n// A proper path flattening algorithm belongs in kurbo. In the meantime, this will let us\n// get something rendered.\n\nuse kurbo::{BezPath, CubicBez, PathEl, Point};\n\npub fn flatten_path(path: &BezPath, tolerance: f64) -> Vec<Vec<Point>> {\n    let mut result: Vec<Vec<Point>> = Vec::new();\n    let mut cur_path = None;\n    let mut last_pt = Point::default();\n    for el in path.elements() {\n        match el {\n            PathEl::MoveTo(p) => {\n                if let Some(sp) = cur_path.take() {\n                    result.push(sp);\n                }\n                cur_path = Some(vec![*p]);\n                last_pt = *p;\n            }\n            PathEl::LineTo(p) => {\n                cur_path.as_mut().unwrap().push(*p);\n                last_pt = *p;\n            }\n            PathEl::CurveTo(p1, p2, p3) => {\n                let cb = CubicBez::new(last_pt, *p1, *p2, *p3);\n                // This is a really hacky way to get finer subdivision. It will\n                // give overly coarse results if the Bézier is close to cubic. But\n                // close enough for now.\n                //\n                // A reasonable approach would be to subdivide the quads based\n                // on the true error, or we could try to do a fancier algorithm.\n                for (_, _, q) in cb.to_quads(tolerance * 1e-2) {\n                    cur_path.as_mut().unwrap().push(q.p2);\n                }\n                last_pt = *p3;\n            }\n            _ => (),\n        }\n    }\n    if let Some(sp) = cur_path.take() {\n        result.push(sp);\n    }\n    result\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//  Copyright 2019 The xi-editor authors.\n\nuse std::mem;\nuse std::ptr::copy_nonoverlapping;\nuse std::str::FromStr;\n\nuse kurbo::{BezPath, Circle, Line, Point, Rect, Shape, Vec2};\n\nuse roxmltree::Document;\n\nmod flatten;\n\n// Keep these in sync with PietShaderTypes.h\n\n#[repr(C)]\n#[derive(Clone, Copy)]\nstruct SimpleGroup {\n    n_items: u32,\n    items_ix: u32,\n}\n\n#[repr(C)]\n#[derive(Clone, Copy, Default)]\nstruct ShortBbox([u16; 4]);\n\n#[repr(C)]\nunion PietItem {\n    circle: PietCircle,\n    stroke_line: PietStrokeLine,\n    fill: PietFill,\n}\n\n#[repr(C)]\n#[derive(Clone, Copy)]\nstruct PietCircle {\n    item_type: ItemType,\n}\n\n#[repr(C)]\n#[derive(Clone, Copy)]\nstruct PietStrokeLine {\n    item_type: ItemType,\n    flags: u32,\n    rgba: u32,\n    width: f32,\n    start: (f32, f32),\n    end: (f32, f32),\n}\n\n#[repr(C)]\n#[derive(Clone, Copy)]\nstruct PietFill {\n    item_type: ItemType,\n    flags: u32,\n    rgba: u32,\n    n_points: u32,\n    points_ix: u32,\n}\n\n#[repr(C)]\n#[derive(Clone, Copy)]\nstruct PietStrokePolyLine {\n    item_type: ItemType,\n    rgba: u32,\n    width: f32,\n    n_points: u32,\n    points_ix: u32,\n}\n\n#[repr(u32)]\n#[derive(Clone, Copy)]\nenum ItemType {\n    Circle = 1,\n    Line = 2,\n    Fill = 3,\n    StrokePolyLine = 4,\n}\n\npub struct Encoder<'a> {\n    buf: &'a mut [u8],\n    free_space: usize,\n    group_count: usize,\n    group_ix: usize,\n    // Start index of currently open group.\n    group_start: usize,\n}\n\nimpl ShortBbox {\n    fn from_rect(rect: Rect) -> ShortBbox {\n        ShortBbox([\n            rect.x0.floor().max(0.0).min(65535.0) as u16,\n            rect.y0.floor().max(0.0).min(65535.0) as u16,\n            rect.x1.ceil().max(0.0).min(65535.0) as u16,\n            rect.y1.ceil().max(0.0).min(65535.0) as u16,\n        ])\n    }\n}\n\nfn point_to_f32s(point: Point) -> (f32, f32) {\n    (point.x as f32, point.y as f32)\n}\n\nimpl<'a> Encoder<'a> {\n    pub fn new(buf: &mut [u8]) -> Encoder {\n        Encoder {\n            buf,\n            free_space: 0,\n            group_count: 0,\n            group_start: 0,\n            group_ix: 0,\n        }\n    }\n\n    pub fn alloc(&mut self, size: usize) -> usize {\n        let result = self.free_space;\n        self.free_space += size;\n        result\n    }\n\n    // It's probably better to do this without unsafety (after all, we're just creating bytes).\n    // Probably the thing to do is write proc macros.\n    pub unsafe fn write_struct<T>(&mut self, ix: usize, s: &T) {\n        let len = mem::size_of::<T>();\n        //println!(\"writing {} bytes at {}\", len, ix);\n        copy_nonoverlapping(\n            s as *const T as *const u8,\n            self.buf[ix..ix + len].as_mut_ptr(),\n            len,\n        );\n    }\n\n    pub fn begin_group(&mut self, n_items: usize) {\n        let item_start = mem::size_of::<SimpleGroup>() + n_items * mem::size_of::<ShortBbox>();\n        let total_size = item_start + n_items * mem::size_of::<PietItem>();\n        self.group_start = self.alloc(total_size);\n        self.group_count = n_items;\n        let group = SimpleGroup {\n            n_items: n_items as u32,\n            items_ix: (self.group_start + item_start) as u32,\n        };\n        unsafe {\n            self.write_struct(self.group_start, &group);\n        }\n    }\n\n    pub fn end_group(&mut self) {\n        assert_eq!(self.group_ix, self.group_count);\n        // This will get more interesting when we have nested groups.\n    }\n\n    unsafe fn add_item<T>(&mut self, item: &T, bbox: ShortBbox) {\n        assert!(self.group_ix < self.group_count);\n        let bbox_ix = self.group_start\n            + mem::size_of::<SimpleGroup>()\n            + self.group_ix * mem::size_of::<ShortBbox>();\n        self.write_struct(bbox_ix, &bbox);\n        let item_ix = self.group_start\n            + mem::size_of::<SimpleGroup>()\n            + self.group_count * mem::size_of::<ShortBbox>()\n            + self.group_ix * mem::size_of::<PietItem>();\n        self.write_struct(item_ix, item);\n        self.group_ix += 1;\n    }\n\n    // Encode a circle. Currently this has a lot of limitations and is mostly used for debugging\n    // and performance analysis, but could be expanded to the real thing.\n    pub fn circle(&mut self, circle: &Circle) {\n        let piet_circle = PietCircle {\n            item_type: ItemType::Circle,\n        };\n        unsafe {\n            self.add_item(&piet_circle, ShortBbox::from_rect(circle.bounding_box()));\n        }\n    }\n\n    // Should these be by reference or move?\n    pub fn stroke_line(&mut self, line: Line, width: f32, rgba: u32) {\n        let piet_stroke_line = PietStrokeLine {\n            item_type: ItemType::Line,\n            flags: Default::default(),\n            rgba: rgba.to_be(),\n            width,\n            start: point_to_f32s(line.p0),\n            end: point_to_f32s(line.p1),\n        };\n        // TODO: do we need to add an additional 0.5?\n        let hw = (width * 0.5) as f64;\n        let bbox = line.bounding_box().inflate(hw, hw);\n        unsafe {\n            self.add_item(&piet_stroke_line, ShortBbox::from_rect(bbox));\n        }\n    }\n\n    // Signature will change, need to deal with subpaths and also want curves.\n    pub fn fill(&mut self, points: &[Point], rgba: u32) {\n        let (points_ix, bbox) = self.encode_points(points);\n        let piet_fill = PietFill {\n            item_type: ItemType::Fill,\n            flags: Default::default(),\n            rgba: rgba.to_be(),\n            n_points: points.len() as u32,\n            points_ix: points_ix as u32,\n        };\n        unsafe {\n            self.add_item(&piet_fill, ShortBbox::from_rect(bbox));\n        }\n    }\n\n    pub fn polyline(&mut self, points: &[Point], rgba: u32, width: f32) {\n        let (points_ix, bbox) = self.encode_points(points);\n        let piet_poly = PietStrokePolyLine {\n            item_type: ItemType::StrokePolyLine,\n            rgba: rgba.to_be(),\n            width,\n            n_points: points.len() as u32,\n            points_ix: points_ix as u32,\n        };\n        let hw = (width * 0.5) as f64;\n        unsafe {\n            self.add_item(&piet_poly, ShortBbox::from_rect(bbox.inflate(hw, hw)));\n        }\n    }\n\n    pub fn encode_points(&mut self, points: &[Point]) -> (usize, Rect) {\n        let points_ix = self.alloc(points.len() * mem::size_of::<(f32, f32)>());\n        let mut dst = points_ix;\n        let mut bbox = None;\n        for &pt in points {\n            bbox = match bbox {\n                None => Some(Rect::from_points(pt, pt)),\n                Some(old_bbox) => Some(old_bbox.union_pt(pt)),\n            };\n            unsafe {\n                self.write_struct(dst, &point_to_f32s(pt));\n                dst += mem::size_of::<(f32, f32)>();\n            }\n        }\n        let bbox = bbox.expect(\"encoded empty points vector\");\n        (points_ix, bbox)\n    }\n\n    #[allow(unused)]\n    fn debug_print(&self) {\n        unsafe {\n            for i in (0..self.free_space).step_by(4) {\n                println!(\n                    \"{:04x}: {:08x}\",\n                    i,\n                    std::ptr::read((self.buf.as_ptr().add(i) as *const u32))\n                );\n            }\n        }\n    }\n}\n\n#[allow(unused)]\nfn make_cardioid(encoder: &mut Encoder) {\n    let n = 97;\n    let dth = std::f64::consts::PI * 2.0 / (n as f64);\n    let center = Point::new(1024.0, 768.0);\n    let r = 750.0;\n    encoder.begin_group((n - 1) * 2);\n    for i in 1..n {\n        let p0 = center + Vec2::from_angle(i as f64 * dth) * r;\n        let p1 = center + Vec2::from_angle(((i * 2) % n) as f64 * dth) * r;\n        encoder.circle(&Circle::new(p0, 8.0));\n        encoder.stroke_line(Line::new(p0, p1), 2.0, 0x000080e0);\n    }\n    encoder.end_group();\n}\n\n#[allow(unused)]\nfn make_path_test(encoder: &mut Encoder) {\n    encoder.begin_group(1);\n    encoder.fill(\n        &[\n            Point::new(10.0, 10.0),\n            Point::new(15.0, 800.0),\n            Point::new(300.0, 500.0),\n        ],\n        0x80e0,\n    );\n    encoder.end_group();\n}\n\nfn make_tiger(encoder: &mut Encoder) {\n    let scale = 8.0;\n    let tiger_svg = include_bytes!(\"../Ghostscript_Tiger.svg\");\n    let doc = Document::parse(std::str::from_utf8(tiger_svg).unwrap()).unwrap();\n    let root = doc.root_element();\n    let g = root.first_element_child().unwrap();\n    let mut n_items = 0;\n    for path in g.children() {\n        if path.is_element() {\n            let d = path.attribute(\"d\").unwrap();\n            if let Ok(ref bp) = BezPath::from_svg(d) {\n                let xform_path = kurbo::Affine::scale(scale) * bp;\n                if path.attribute(\"fill\").is_some() {\n                    n_items += count_fill_items(&xform_path);\n                }\n                if path.attribute(\"stroke\").is_some() {\n                    n_items += count_stroke_items(&xform_path);\n                }\n            }\n        }\n    }\n    println!(\"{} items\", n_items);\n    encoder.begin_group(n_items);\n    for path in g.children() {\n        if path.is_element() {\n            let d = path.attribute(\"d\").unwrap();\n            let bez_path = BezPath::from_svg(d);\n            if let Ok(ref bp) = bez_path {\n                let xform_path = kurbo::Affine::scale(scale) * bp;\n                if let Some(fill_color) = path.attribute(\"fill\") {\n                    encode_path(encoder, &xform_path, parse_color(fill_color));\n                }\n                if let Some(stroke_color) = path.attribute(\"stroke\") {\n                    let width = f32::from_str(path.attribute(\"stroke-width\").unwrap()).unwrap();\n                    let width = width * (scale as f32);\n                    let color = parse_color(stroke_color);\n                    encode_path_stroke(encoder, &xform_path, width, color);\n                }\n            }\n        }\n    }\n    encoder.end_group();\n}\n\nconst TOLERANCE: f64 = 0.1;\n\nfn count_fill_items(bezpath: &BezPath) -> usize {\n    let flattened = flatten::flatten_path(bezpath, TOLERANCE);\n    flattened.len()\n}\n\nfn count_stroke_items(bezpath: &BezPath) -> usize {\n    let flattened = flatten::flatten_path(bezpath, TOLERANCE);\n    flattened.len()\n}\n\nfn encode_path(encoder: &mut Encoder, bezpath: &BezPath, rgba: u32) {\n    let flattened = flatten::flatten_path(bezpath, TOLERANCE);\n    for subpath in &flattened {\n        encoder.fill(subpath, rgba);\n    }\n}\n\n// This is a tradeoff between smoothness and contrast, set by aesthetic preference. The\n// optimum rendering of very thin strokes is likely an area for further research.\nconst THIN_LINE: f32 = 0.7;\n\nfn encode_path_stroke(encoder: &mut Encoder, bezpath: &BezPath, mut width: f32, mut rgba: u32) {\n    // Fudge very thin lines to get better distance field rendering.\n    if width < THIN_LINE {\n        let alpha = (rgba & 0xff) as f32;\n        // The sqrt here is to compensate for \"correct\" alpha blending.\n        // We probably want a more systematic approach to stroke thickening.\n        let alpha = alpha * (width / THIN_LINE).sqrt();\n        rgba = (rgba & !0xff) | (alpha as u32);\n        width = THIN_LINE;\n    }\n    let flattened = flatten::flatten_path(bezpath, TOLERANCE);\n    for subpath in &flattened {\n        encoder.polyline(subpath, rgba, width);\n    }\n}\n\nfn make_test_scene(encoder: &mut Encoder) {\n    //make_cardioid(encoder);\n    //make_path_test(encoder);\n    make_tiger(encoder);\n}\n\nfn parse_color(color: &str) -> u32 {\n    if color.as_bytes()[0] == b'#' {\n        let mut hex = u32::from_str_radix(&color[1..], 16).unwrap();\n        if color.len() == 4 {\n            hex = (hex >> 8) * 0x110000 + ((hex >> 4) & 0xf) * 0x1100 + (hex & 0xf) * 0x11;\n        }\n        (hex << 8) + 0xff\n    } else {\n        0xff00ff80\n    }\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn init_test_scene(scene_buf: *mut u8, buf_size: usize) {\n    let buf_slice = std::slice::from_raw_parts_mut(scene_buf, buf_size);\n    let mut encoder = Encoder::new(buf_slice);\n    make_test_scene(&mut encoder);\n    //encoder.debug_print();\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "#[macro_use]\nextern crate piet_gpu_derive;\n\n//#[derive(PietMetal)]\nstruct SimpleGroup {\n    n_items: u32,\n    items_ix: u32,\n    // TODO: bbox\n}\n\npiet_gpu! {\n    mod scene {\n        struct SimpleGroup {\n            n_items: u32,\n            // This should actually be a variable size array.\n            items_ix: Ref<PietItem>,\n            // Note: we want a variable size array of bboxes\n            bbox: [u16; 4],\n        }\n        struct PietCircle {\n        }\n        struct PietStrokeLine {\n            flags: u32,\n            rgba_color: u32,\n            width: f32,\n            start: [f32; 2],\n            end: [f32; 2],\n        }\n        struct PietFill {\n            flags: u32,\n            rgba_color: u32,\n            n_points: u32,\n            points_ix: Ref<f32>,\n        }\n        struct PietStrokePolyLine {\n            rgba_color: u32,\n            width: f32,\n            n_points: u32,\n            points_ix: Ref<f32>,\n        }\n        enum PietItem {\n            Circle(PietCircle),\n            Line(PietStrokeLine),\n            Fill(PietFill),\n            Poly(PietStrokePolyLine),\n        }\n    }\n}\n\npiet_gpu! {\n    mod ptcl {\n        struct CmdCircle {\n            // In existing code, this is packed; we might need an annotation for this.\n            bbox: [u16; 4],\n        }\n        struct CmdLine {\n            start: [f32; 2],\n            end: [f32; 2],\n        }\n        struct CmdStroke {\n            // In existing code, this is f16. Should we have support?\n            halfWidth: f32,\n            rgba_color: u32,\n        }\n        struct CmdFill {\n            start: [f32; 2],\n            end: [f32; 2],\n        }\n        struct CmdFillEdge {\n            // The sign is only one bit.\n            sign: i32,\n            y: f32,\n        }\n        struct CmdDrawFill {\n            backdrop: i32,\n            rgba_color: u32,\n        }\n        struct CmdSolid {\n            rgba_color: u32,\n        }\n        enum Cmd {\n            End,\n            Circle(CmdCircle),\n            Line(CmdLine),\n            Fill(CmdFill),\n            Stroke(CmdStroke),\n            FillEdge(CmdFillEdge),\n            DrawFill(CmdDrawFill),\n            Solid(CmdSolid),\n            Bail,\n        }\n    }\n}\n\nfn main() {\n    print!(\"{}\", gen_gpu_scene(\"MSL\"));\n}\n"
  }
]