pax_global_header00006660000000000000000000000064144155074170014522gustar00rootroot0000000000000052 comment=ab6e6f34077722d5ae33f6bd40b18ef9c0e99a15 vkmark-2017.08+git20220909/000077500000000000000000000000001441550741700146055ustar00rootroot00000000000000vkmark-2017.08+git20220909/.github/000077500000000000000000000000001441550741700161455ustar00rootroot00000000000000vkmark-2017.08+git20220909/.github/workflows/000077500000000000000000000000001441550741700202025ustar00rootroot00000000000000vkmark-2017.08+git20220909/.github/workflows/build.yml000066400000000000000000000011511441550741700220220ustar00rootroot00000000000000name: build on: push: branches: - master pull_request: branches: - master jobs: build: strategy: matrix: os: [ubuntu-20.04, ubuntu-22.04] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 - name: Install dependencies run: > sudo apt install meson libvulkan-dev libglm-dev libassimp-dev libxcb1-dev libxcb-icccm4-dev libwayland-dev wayland-protocols libdrm-dev libgbm-dev - name: Setup run: meson setup build - name: Build run: ninja -C build - name: Run tests run: meson test -v -C build vkmark-2017.08+git20220909/.gitignore000066400000000000000000000000111441550741700165650ustar00rootroot00000000000000/build*/ vkmark-2017.08+git20220909/COPYING-LGPL2.1000066400000000000000000000636421441550741700166300ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! vkmark-2017.08+git20220909/README.md000066400000000000000000000064351441550741700160740ustar00rootroot00000000000000![](https://github.com/vkmark/vkmark/workflows/build/badge.svg) vkmark is an extensible Vulkan benchmarking suite with targeted, configurable scenes. # Building requirements To build vkmark you need: * the meson build system * a C++14 compiler * libvulkan and development files * libglm development files (header only library) * libassimp and development files for the X11 backend: * libxcb and development files * libxcb-icccm and development files for the wayland backend: * libwayland-client and development files * wayland-protocols >= 1.12 for the KMS backend: * libdrm and development files * libgbm and development files On a recent Debian/Ubuntu system you can get all the dependencies with: `$ sudo apt install meson libvulkan-dev libglm-dev libassimp-dev libxcb1-dev libxcb-icccm4-dev libwayland-dev wayland-protocols libdrm-dev libgbm-dev` # Building and installing vkmark uses the meson build system. To configure vkmark use: `$ meson build` This will create a vkmark build configuration in the **build** directory. To see/set the full list of available options use: `$ mesonconf [-Dopt=val] build` To build use: `$ ninja -C build` To install use: `$ [DESTDIR=...] ninja -C build install` # Running After installing you can run vkmark with: `$ vkmark [options...]` You can run vkmark from the project directory without installing with: `$ build/src/vkmark --winsys-dir=build/src --data-dir=data [other-options...]` # Benchmarks vkmark offers a suite of scenes that can be used to measure various aspects of Vulkan performance. The way in which each scene is rendered is configurable through a set of options. To list the available scenes and their acceptable options you can use the `-l, --list-scenes` command line option. In vkmark, a benchmark is defined as a scene plus a set of option values. You can specify the list and order of the benchmarks to run by using the `-b, --benchmark` command line option (possibly multiple times). If no benchmarks are specified, a default set of benchmarks is used. If a benchmark option is not specified it assumes its default value (listed with `-l, --list-scenes`). As a special case, a benchmark description string is allowed to not contain a scene name (i.e. to start with ':'). In this case, any specified option values are used as the default values for benchmarks following this description string. # Benchmark examples To run the default benchmarks: `$ vkmark` To run a benchmark using scene 'vertex' with a 'duration' of '5.0' seconds and 'interleave' set to 'false': `$ vkmark -b vertex:duration=5.0:interleave=false` To run a series of benchmarks use the `-b, --benchmark` command line option multiple times: `$ vkmark -b vertex:duration=5.0 -b clear:color=1.0,0.5,0 -b cube` To set default option values for benchmarks and run them: `$ vkmark -b :duration=2.0 -b vertex:interleave=true -b vertex:interleave=false -b :duration=5.0 -b cube` To set default option values for the default benchmarks and run them: `$ vkmark -b :duration=2.0` # Window system selection vkmark tries to automatically detect the most suitable window system to use. If this fails, or you want to override the automatic detection mechanism, you can use the `--winsys` command-line option. For example, to force the XCB window system use: `$ vkmark --winsys xcb` vkmark-2017.08+git20220909/data/000077500000000000000000000000001441550741700155165ustar00rootroot00000000000000vkmark-2017.08+git20220909/data/meson.build000066400000000000000000000004611441550741700176610ustar00rootroot00000000000000install_subdir( 'shaders', install_dir : join_paths([get_option('datadir'), 'vkmark']) ) install_subdir( 'models', install_dir : join_paths([get_option('datadir'), 'vkmark']) ) install_subdir( 'textures', install_dir : join_paths([get_option('datadir'), 'vkmark']) ) vkmark-2017.08+git20220909/data/models/000077500000000000000000000000001441550741700170015ustar00rootroot00000000000000vkmark-2017.08+git20220909/data/models/cat.3ds000066400000000000000000007044421441550741700201760ustar00rootroot00000000000000MM" ==FMaterial.002 ```  0 @ƈMesh1_MateriAAX,?ǭ>_?lF?>ҵ?;?>-??8>??????%??>Vx?K9??jh??>oh?Y?>y??g>l]?s?>=(?$?8>F?vj???(Y????>!?d?g>SH#?v?>$??8>I&???$???z*'? ?>&??g>(??>z*'??8>(??? )???=)??ǭ>E&?Z{?>&'???>$?k?>L&?`??(?5??z*'?c?I??Pg?nY??;?n ?-?2|?. ?\??n ?jh??TH???I?z??sk?V?s?n ?8(?P? ?In?"?I?p&?W?sk?~#?v?n ?$?? ?n!??I?C )?Q?sk?9[(??n ?z*'?? ?u&?cd?TH?&?u?nY?(???n ?$??. ?c;&?K>?vR?? ?o? ?s??j?b?? ?(Y??jh?ZH?Q?v?E?vR??r?i???? ?pJ??Vc??vR?>!?%?i????8(?Y????vR?#?su.?zi?8>%0?P?#>0}/?P?{>1?Q?Ef?1?P?Ef?2?4y?8>.??Ef?0}/?3?#>%-?Q?.>.?)? >n0?Mn?#>%-? ?{>k.??Ef?`1?*?Ef?0}/?|?.>^F*?~? >+?z?#>z&?[y?{>'?#~?Ef?Ih,?Sy?Ef?E(?w?.>!?u? >!"?/7t?#>k?r?{>?t?Ef?F9#?q?Ef?x?Dr? >{??q?#>%?Mp?8> ?rMp?Ef?1? Wo?Ef??P??[1? o? ?nh0?P?:?/}/?}??e$?ؾ?:?@d'?v?h?`!?zӁ?8?;($? Mw??:9?y?:?P?z?h?b?4}?8? ?8u?=?s?v?)??Sy?h? ?y??F??T^?^a?T-?)?_? ?j?F4?K?A?8.? L?> @?i??' ?I?>@??G>d??>?^?\>?R?$>??6>|W?6?E>$?? >&?5?|>- ?W?#>\#?~ ?G>v)??p>/?.?,>ܲ/?|z?)>S7?u?>_ '??y>=3?@3?3>M1??*? >=+?>?B>k,)?:|?͋>9?Ó?)>V:?%?M`>O6?pI?>%x4?? ?=/??>K-??8>O;?0?=[>p@?b?/?D?|Rf?$:?ۺ?o??$?l?_G?%?]j??4 ?s??#?oJp?)% ?e?wl? ??Se? ??0_?+Q ?E?s? ?&*?{??5-?z?M!?.?*u?=?(?{}?u?y)?x?a?&?Ł?"?2??7?3?(0?#?6?ā??M0?|?OT?{/?'??,?d|?d ?8?L?=?8?:Ŏ??];?? T?5?쏊??T(2?N(??uF0?≑??=?Ȏ??}9??1T?@?ͥ?W ?ZF? ?#?H?ؒ??@?|? >C??>=?lr?>eC?@?M>Ffb4??s>U ,SD?M?M>;? 0?>E?3?R> 2?ч?&>'3?o?_?̙?n?>%?A$s?u>z#?i?J?L ?g%i?>%?FRd?r? ?Ȫx?U>'?ft?>$*?&~?>,?.z?8> 0?JNp?$A>,?])w?Ͷ>W3??u>;?~?W]>5?]-?\l>Q.?݅? ?:,?W?T?^-?|?i.?.?T?d?*?? ?@h+?X?.?)?~?7?&?,?s?Lb)?jQ?@"?H&?0?!c?C#?Ȱu??[ ?Cz?s?#?{?? ?ˠw?x ??vu? ??}s?H??Q?su?e?) !?_Gr?J?t?$u?&??aw?'???>c)?z?> $?ܨ!?$?>%?ϳ?>f.?3҂?'>8+??9>%{(?nC?\>k+?l?M>0?0?>}/?'?3|>G,?ߊ?J>K-?&?8V?͐2?? $?-??v?1?ތ?-?x.?I?;?r0?I?,?n,?n?W_>$? ?1>A&??>@(?(D?>="?b?*>$??k>] ??>V??$>& ??8>?? >C??>U*?g?D>V?5?>}?Z?8O>ʆ??_? ?%Q??;?Q??Bf?g??< ?/? ?ŧ??E ?Q?6?d? ?? ?WT?d?!O????h?C?"?y??]?>B??E?!?+?0??P?F??>?1H?4?? ?E'??+?d$???~@!?\?1j ?4&$?Yֿ??)+?:?J?8)??M?e%???'?[?v>*??>H&?MXuoNɬ͊m`W,toڬil !OWWd~W{xd޽W9e[۽جm :ٽWCu @wgs ֬Ll,ټWc"c=Wbx=Ѭ6jw=W~q0 >5rm~>ZѬee,=Wv]~L>{ˬa3D>W)XT>W4h軘>_ b>ĬZ>WWQd>ǬšR>WI"'>W[r?;}VѾ?~{N?W E?@T-ȾZ@ ;G^@W@WG5 @v4 @W-` @̒V쵾C @ e׭@Wzm @W!X @Tգ @W? @mQ @L @WG @rˉ @WmA6 @WϾ@*XG @w~ @WjuE @WaO @2i @W~p@U@hc?s @Ma[ @Wd^q @WG[ @X_{@WPVrO@c6Mwv @%NSi @W#ZX @'Ma @WY@WCu@U/N @\鬿\9~ @WD @,%a @W0 @WB @Y' @" @W w| @Wc^q @t,2 @Wlf齄 @ uvý @ > @W.@W @lȽ @W; @@gK @ J@WR @lq@Wb @W@hYY\f@笿M@Wvj@%h@WսR@Wf5#@`)Y@)zs@WtB@WlT"B@$쬿KM@Wz^@lqfS@W9kC$@3@WgϽ?W ?I{u\(?۬Ҩ?W9w?ʽ&?W=?Wlr? \lI%?FX˽?W YI?WW =n?_Wн?WW.?:˽tV?FDK?W%\?_6m?W>ɛ?W ?\5T?ି^O?Wnt?U ?W攏p?Wjf=?K΍).?լ𷚾M?W>?8ǵ>?W ľ~?WJcK?*ag?Lž`?Ws<־,?ᬿ_־4?W܏X}?WMƾ&?櫿ɾCi?rYپ?W꾕?c^۾o?W=쾟ʜ?W˾?^ʫTͩ?Y?W?W?jrv?W?T'I&? gY?Weik?Wt&O!?=@4?WƟ?7Cv\?Ib${?Wvֽy?쨬 Prq?W/A_H?W̢y?ʫ= r?X_l=3j?WI=b?W>&Bs?W>s?jiS(h>PGl?W/p>o|?׬>u?W>9?Wޒ>9? > s?#$'>N?WP>?묿a>!>?WԖ> ?WU>}x?.>? W>u?WF>?Wg{ ?|?V﬿}?tW?WLI>\?(_??ݬM ?Nز?W ? ?WN??o??Wֲ??l?+?p㬿."??W)?0?W+4?:?-?-?W&??Um*?ty?WŠ7?_?묿 =4?(?W;?0?ެ0?i?W.??VHv2??^ܬ8?G4?WV????WD??so=??W6??{;?)?񬿢A?Y?WeH?@s?WL?;?z謿XF?]Y?WN,A?t?yo#D?\?EH?"?W`wM?O?W3N?+?~J?~?W>G?[?`pM??쬿Q??WߕT?DD?GPW??WEZ??WzT? ?ThX?(?)쬿Z?_?WT]??Wc`??w:^?2?WV\? J?nL`?i?: a?+?Wa?vl?WEc??(c??WJd?0?^i? ?%g??W~e??O^k?FG?Wg??W*Mo??_es?6?&o??W?k?v?񬿼s?Ib?Wn? ?W x?4?U4z?r?笿;sv??W2^q??Ws?c ?ꬿ&y??W}??%`)?H??pꬿ`{?ZV?W>u??Wވv?tm?+笿|??WS?%?e_? ?쬿l~??Ww??ꬿ?Q?Wm~x?9s?W[?iq?Ocط?P?ꬿ7?]?Wz?W?W?e6?Wҕ|?ih?W??e~u??Oˤ?^?W&~?Y?W=R??t˄?"o?W??z?;?'҃??W7~?: ?.󬿣?vw?W?}??WG?ܸ?vRO?D?쬿}?N(?Wu??.5~u?(?Wm?;?WX|?*?Eoq?z?ҬSj?6?W:b?!?V _?E?WnW??W f??T7&`[?wä?ȬCT?!Ħ?WL? ?= I??WA?v?WQ??e< G??NǬ@?Ð?W_8?@ ?!謿66?n?W /?&?W9>?}w?AOo9?y?Ŭ\2?M|?W;*?k?ᬿq-?En?Wj%?i?W4?B\?DHK12?]?Ȭw*?_?W?"?gP?)嬿'?sQ?W?N?W/?&@?taa-?A?٬J^&?"C?W?4?W?(3?Aꬿ<$?1?W+?$?]{(?&?謿։!?n(?W(?o ?Wm?6-?謿)=?ӛ?Wd%? ?燬?x?쬿(?jN?W?j?W ?c?䬿φ?`>W?>VsԲ?=6>⬿ ?x#?W9?b6>鬿ۙ> i>Wx>>W1[?>2>K>m>_>W">ٻ>:>V>W̨>>Wk>xF>E˫>O>:>X>Wu>&H=m𬿔ޛ>2=W0>ԍ=Wl>;>Ou>` W]> m>WkǕ>gWД>j1s> 9ڽA>c W4>/WB>y6>DWN>9w̽W>4>"W>(WP> (>ܽW(>[V>]7>3i4W >? ,L>~@Wr>k3W>d Z?),$?DWQ>HWk?)W ?W ?ss?2n " ?[OW?wVWg?~< ?&WFx?=;~]?rP?kiW ?dv?(|WX?:PW=?JbF[!?7it? WG?̌W7?Vn !?tW'?WȖ*?m#?0'WwM?߁Wc?Akm&?W-? d_.?W&?W?Z:y&?W?>W h.?v پ_=)?վv!?4ѾW2d?'Ѧ?MW ?Wp#? !?" Q{?W ?O"?W?rW?J$g?b%|Dl?c&WC ?2̬n?/6W ?b.W?٦4꫿ &?9Hr$x ?wz?WD?HW(?_A -?K:WP1?BA@?H;?CPWq6?iWWn`D?PJ?IWO?_g0\?id@\0U?iWOM?]px⬿_?|W>V?tWh?bq4)-j?$*%a?኿Wz4X? W*Z?&w嬿ob?zW j?&d?w]?W,U?qWVP?ﭿ W?_KW^?񔹿vU?%䬿 O?I`WG?#¿-󬿹E?NW[=?{ÿW L?Iο A?̿c묿!:?\ʿWZ 3?9տWZ(?r׿E﬿\/?vٿW5?͆)?`߿쬿D#?ܿW?T/W>?묿){?W%? 򿰂{ ?Eꬿ?"W?W>1ez>!'WC>`;B>1묿>/Wo5>Wm>t>WB>nz>&|Ɛ>;Wq;>նW/F>lT>j Wb>t  >` b =sW;=d/ W=S v"=? WIr9= ᫬l  S W%B W;꽓 $ Wb,# _AEK )Z; WP4JW0tE 3 WI_G ha}WEwW!sH[@9ľWJ;B龃OYUdWԾpX,WLjc.W~KT zSЬW$Æ0䬿=dW:W $w翟Va4a俀Ϭ/GW(ٿW!w8ݿ㬿>g>߿W@C.տ)XҿRϿWnLſW_iȿ/㬿mf˿W$lR3z|/s3?W&lӉWЙxITqެWpqUdP릿W,MWWDŽrR߬爿EW/츬 CŠaW_aWm!ܬ/WB}k;F{lDTmW􅿽UW탿fS8ܬ+QW7C:Un]<߬ ?W߲~ %ج'}R(W uE"Wa1$}6v WQnJӬ{-oJWRgWvgľD\uƾ nǾWe3<٤>G$K<5>أ=䶤F>.= #><ꎽ0 > O߾> $J>ʛF>e>½>aӽæW>ȽwݩS>-ؽǩh1>uԦ)>cDh;>2ө`0>E8??C>.M?T?%x |b; ?e|D ?Z#?@?}*y:O?u4v&?TnJ?Ix(9!?H_38h?Ɛ? V?q(L$?7f9#?xiEt)?-)?0j|{-?a+0?O.?]YL%`1?q>gm2?ϭn1?R6K;1?]?1? ĥǣ,?y޾+??|%?F~%?bߥ ?:|l?no?٥?%n=?%%(P?,Jl=5??+ߠ?A% g?s Fsu?@#?vFTq?FJϗA{?hBll?|Q6-?i>;]?Y@s?]Cj ?"E¯te?o Ic$ ?lG 8?ULl?/O2íH?Iz?2?SQª?sP|l|?/H2f?!Omk=?LD0܍?*)D(?(%ݲG?7`"$gI?}wʾE?/žg?k Gkо+?"'X?vט?t =ܾh? Iͅ?XV2┑?~t?&D?6~ 4?v?J+f^? xE7? ; |? !/Ւ?t+O_? ')?Y;=?8Ⰵ?l _>H?P}QI?< P?PN?$0e?ONdKw?=BdL?YDvۃ? x}?4xD7p?\{u?JGg?*i9p?(EE|?_ʏTp?q-b?p ʙs[?)p(j?4v?%Bs?5f/}Zg?*kݟH Y?Ib5HAV?JB5c?9Q |o?cpy l?5PX`?,?^N)S?P-#p?Y"  ?$$Rʘ?& j?X4=Ӝ?l()?T6أџ?0`aD?Y(Ծ?0zپY?#`_?7,+_?$1?;K1 ?'K??4$>Y?&N9t?P?wXb0`?)_%?Յ.oa?L"sY?(‚CY?4uiΆ?>Vɉ i?l3U?>ĕ~?+Ie؁?Ejb/jt?^}?gs죿5w?.sPĻBz?t0ɨ ^q?eEž]?M>LþU?TJ;ɏھ? D` ۾PY?d6p ?^9-@U[?B ?%&DwMx??p4̙? ]3%N?A3X?S@$?s@@0hf?*3ʱ1 ?2~=?e@O;?FBIT?4LŎ?7*[?+DW?2H,mg?e;6lf?AXp|1?SyMCw*ʅ?AV0?JxɄ?RT Q?s_iŠ/ԁ?oXPI?-\gz?*yS{?ԕn{?(3?%%~?'\v?`$?w ?7򯡿l~?l#i2?E ?'!8 ?9`?ytj?K H|p?"-!?!7-q?Ʀ'?QWя ?"] ?x=;?y/?Q} u'?'Nv7?& ݤj.?!zq"?9_'])? RtBUD?[ e29?/jKK?|˥@?pQX2?g2M=?#휝E8?"$,?*'?T-.@3?- (?|2AਿX2?0uҨDR'?'֎\kS?-C mY? 혿L?,%LKxH?~047+E?8uC?j,k[H?-4tS ?7xw?Gk#a ?#soL?]FukK?rS b"?<}Xi`%?@j?oZN-O\@!?=8e/?*}?K!f#̕?g c~3?/t?jđV? :վ˱? =Aоt?0aH?Bu)?"r?N+~??"?`?"?qqD{?M=꾠2?.*-s? Iu?n徏=DDg? -Wk`j?;[Ɠ[?8hdtb?8^oKS?s"ly?$GIŰ?L o?V%p?! ž?Yu@?-GG۾-? OҾ{? 1~?Ns?.baw?: hCk?BY?vc?F'o?I, ,'\?8MK&73g?kH7{P?(龾=[?OtF?|\"UeP?(db >?B5i+H?..`@ ?U)0:?&ўƄ?cF?je,d{?p*\?GCȾn?a˾t? \X,k?jx9qd?U)]b?Bh<[?Pru Z?#u R?s`R?1J?M *:I@?b0KG? #=7?Fz=?/Xo5?%EN/?"2sU?&?6hcb ?~_4>PM:'?C&͏iJ?݈12?YG-?gE7,?Ü63?..~_1V3?t .y"p;?Ձu%A =v\?"Z޾c,_?%ži?2cȾf?jX7d)Zs?Y.+TF+v?T0蜾Y?x;럾`ƀ?34?{e? 6Ӓ&?)D]>J$??J+?8Q0YM+?J I0?H0<$6?B=P=?0?ٌ:'/c6?nE/C?K9/?F?D3>K?FD Q?/:@ oz:n[>43co?U >l] tV[?ohmG?Xl:?}4qT?T#9y(m?fE{?{%B}R" ?&3{J$?-0|0,? 8~{G'??v6?),؆;?n0?|{`??i m8?~H?LJпO?㍿^?eV?706¬]?zr٬Ne?CHFi?uؑa?-Kʒb??.Yj?ڤ}|_i?·v Ia?2ۂNa?驿/g?*^w>Qϭ-o ?5pEX>ڬw?Co}?ⴿ ~aj>g׵t܀o?+# ?uҀ- ?6"qs ?cM,P?g_;pee"?18bv0?RXp(?刿8?wSQȎ@?{LN?^cÿSE?Ŀh䓿FL?akZU?hk]KY?:ſeP?8ſȟauQ?F-cZ?rFϥUY?3ĿEP?ĿDuO?0S dX?Ŀox=>ソypm>֮ĿVpP>oxQ>菼X%#>3Ŀ9>:{ſH>i]>Efみz? TǿO\>u˿Vt?_¿^?pƿ-5?6ϿjX?ӿHS"?CʿCƈX-??Ϳ@:?׿P.?Рٿ, 6?3ϿbnNA?0&пE?TڿIN:?ڿn`>;?.пiBF?ܯϿmE?&Sڿ*=B!:? 2ڿk)9?YeϿ l3yD?Wѿ?xK*>˿j

Zҿ׷o폮>ʿ[x >ܗʿ5Zp>пos> ҿI>˿둁>Ϳ,d>ԿTc,>ٿM>ҿ> ׿'> ?޿넿I=>3ևf ?lۿ<?߿񌿰"?迉l`?꿑)"?mZK)?)d>.?쿮3!?I̞V!?/?am-?gx ?E뿌쨿?⿌E,?ݿq-wN>rؿHn3>D޿Hnv>Û׿w>rb׿Ԇ>ݿV>DJ߿B>TؿiQϣ>ڿZ(1>kῤt3>gm翕 /ݭ>}࿘D$>2>bcU>3kA:>vp>Mۋ?V Jq>@f>lZ ?[ƞ?ր5??h ?$~Gf?pl>5->v1?8鿍vA>Ii俱aniS>'"n&>O+w R>k㿀~W>}U~Q&>Ł3>r'忒ƀe>iꗁx>cK!C>hcl>Âi >J6>/2J>~I>Қ(`>2Ab>xNjLX>&C>UTb>I'0\>/4<>p33d>Pо>Pi>71 #>(>w9p>}u%=0^3nLm=zm=Pv7G=ڰ]%}8g=9φ|R==P~t=c=7؀>  Ě= L= _.4i->CiX胿R>g烿> BI.>>t>y҉>Ql,J>sP/\>aOH:> חܚ> ۗi> 0Anm>2Pq>h좿> pŢn>` Rk>Xⳙ>XBmrxz&keӼ=}jF%&t|~izo_zqy4/p9'~{Un~9b Ns ϗd>x -qK=uQ=U KVN> 1>;$rQ=*A/CK=j m>J? ޽T#́/ ) T \= n U!J}P '9,980: b+ongwh'>gr"pp5x"ޝvZ}Fnv}gQѝwΥY?\5;wU罧V~XH 懾 R˒C գJ]3C?C ĝ䉾~ bۗF? 0 E. .\| bR x捾^G YM2Ȅe|=؆w4@u\4:t6聿m%,pzZn$!þ JϾܱ鮐{оR U!-i CH oxо8[ilоĸ =K g@ɬϾfŀϾhg ߨLW H^sg*`c!s,z A݁3:vbgd)Ni%ߐ\RDAM×~\%:睿.'bR&WZX(e5ܨKKMPU1*t+O th4 *1#=-,iCzMѡ*'Ν?)5LP睿2 )䣿 (56'ӴiF@]g=:u]Jsju _yVG=F Dy݉_Y忦ܐ~;VI~kA0l×9t>ʗ1}R㿔ʝyN[՝{'j?OYNA?Br;D[y?b`c?p4dKhh? t]^&ɖ?v؉fE?r4U?Za|Wx:?/["?aƄbS?^$Y`?^R`? Q M?/S? 1TRn?mjYk?ᐿ],S"?'Z >?H1a?w\ ;?Vf'JU3?s~T?͔[gO{o?yqEp?y{yx|ێ?En9li?4(u}y?S_Ciz?wÜd܂?`"k/k?|k~?‹p||?jR?aؐSi?W?.Q?B۾h?@)߾d?qM?;ֈ`?Y G9I?6g'(\?P:&hrD?k:&WYW?:T䅿Z??VoⅿaW?Sj^B?KlE?ヿ~W? V?RiiE?ۋGɥU?땿(F?&6aU?sf|wF?;Νz[U?ߤ7w0G?Ф_u#I? qV?OAJsɽ6?6lQy:?〿,.?À+&yT9?.,?A񋿧~8?㱖/z+?ŖuS+:?,yu-?6Soj;?FB_n 0?zg(3?+[cm>?]j_/?_-=Dhj0?FmXex͐?)=l33?.KSsu" ?v.6?CS#?'E9?6L%?b`Qc7?D≿ju"?/3Q4?Ɉ,?/?=7u)?<4I)?!0R}?x+N{#?h;ʃ??f k?3HN ?z}Y؀ ?Ɗx?\?q)c ?hM$n?1z?e#?থUa?c/\&?EiTq?P©Wur&?Y\l>]X|>%=p>ﺓr0> x@=Dit"?S{>ؖi߄t?*x΂>ZG% ? ?ZF ܇1>QZ4cW ?KR_n>ɜՐw ?BЍ|>D; ?о}dM ?jy>P:?!m>7Hx?9s3㋿+>?2 s>&N'>Q7fGZ>>fꊿ,>}#>~v]P >Uk7>}>TlApy:>>rK>Ǒo >hib>ySH:e!*>cW_>I.`)>}{W[^>5r=myhM>]I<-=>%=~>T<ы9[>> Aqk+h>I짏V>RNٌr>bH ͭ>(Q 4z>=W]>g~>SžfK>TCɾдl>{t>W|:g4>ș>0w>52T[ī>2;~>1N/>`Nxy> h@u>ފ"q> 4)>gԿʽj>,%a>Hᅿb>a2c>vN)Z^>}ٚ>=WvY>4^s~B>覿lW>nr>Xg=Ӑ=yO>X3=u|ļC<=ق==FPܹ z{@g=̼KuF<0P=YҽE j>>򖿩<=R 뜿ǎ>xK]p*=Ἶa#1>}wCJ=]pT>d=tVL>eq|=D0B >Y1>J=ZLu%!>rMry =T]fa>ogA啿T=̫K(>;ZuD{=, =\>Ӌ⋿n=:0rS>H9Ѕw=f}>t==|9J >\ѥ>9xP˃=>w/ >厩lgs5o=1F]hs=%Bhw=VqY=AቿR÷vF`ҽE~ ER%䕿97)֛.皿v.^tNj.ᒞ%δCסuw֠8g=]QňN=D@+2q]1j 辴Y=(%jo>%t='6⾰&;UP*g:ھٕhN˜'Ѿ<䖾#rʾ}YՐ@,ß¾_LLΊ NiMuK#ф 羟 ʅ X}c}ʢWwT&蟿8s);Zt.@vhDGoF[# (ag_8hv>IY*^yWISFVb⌗懿7R . ڈJgw[V3Pۜ 떾7_W}'Ny>~T#ٴyN'䨿;Hz:>B\\,=2P[.ږ>|9>ݧ=~+*:26 uz;ogN'ϓ3R h!-$7KO^!~zod.{V˳\;5}Tݾɟ!~U>y 5 > %CUE] !+>#E 9OJ7ݾ2=ZpU)ԾqX_.qʾʖsֵmƾ˗iP+]I¾:=]U˥¾h|._%þ=}þeR.gUcbm}V٭Rj= ֔sT ?ȃ񣙿'Y@}r㻛9WMpYrboQ1ᨿ99稿NJfJ:2W!Rv|DUrSRUU;D] X8EQTWmbK;FPbxW=g-&%-frPb#?mU$ ۡk 4qO侳Zᾙנ_vVAv{[f|.y Fx{cˀf"+L3}/bFPȽ8BiNYk^-@8J {rJУ艿 ?⣿?Y&Ԇ1%@? *B0σ«c0䝿ɒ$ vl򗿾fcI3X 񕿨9>D▿0pq Tཝd7rFK9R&ͼ>EPa:|9r^D=uR< \spMyaH~񧐿8鐓ٝǾI1v=᝿Փ/琿f&+ L~߈wRLSwǘz~@ɑ3>|㊿CßςzLψuOg\vR2B\TpG"mLʉ*W[h\$֑zF 0].1Ց)Z<5vᐿSWbiLv0əvrF]U畿a]g@ڰv C&&Dؖ`)m閿[z)%uÚ%>cᕿQN ϰ!N5+=Oˉ̿fp9 ¿#>~ ÿvÀͿ,rhϿZ>vHĿn ſᘘKпryNMҿF}e!ƿsg-ǿa&rӿu!Կm邿큿CCȿL4ɿvr=տ1v)cgP׿e訿ˉ\rؿ %)k_=ٿE1OcwۿOfxܿdjWݿ%imr޿`qm߿uFtr?S'>.?>%|?a-> #UO?Mn>Xjك?JB>Fx?_>Ěމ?>%Yl?J@>.??kG9>Ӄ7SM?ۼL>$>g?J>4y?i>I7+ ?$>.E?\>̃8 ՠ?]>dBs?a,p>IB9?> /F?>8r(?>-|?l>&7͎?~>5#A[;?s<b>[?n=*5?UƮ2?n=*" ?=F؞?Ѿ>QK?ҝ=T?ʛ=UH?,>L?u>>\JL4?>%"T>?OF ?V;? @笜yQ@o{B@a_m @BzevY@(}^-;@C9Ԓ@SKfTp @ V|2@xO @uJ @㟿 A @tA|`@2V2@JurR @䟿t5ZM @)q?n @ޟZ*" @1~ @R&) @mI.A @>D5ٽi @Zԙ @.R#@v @AYR@ K"-< @7ۤu@8.ܽT @:69Th@Xᶽum@w@rrAY%@V(~K@l@PWi@%'x@sI򇋽U@µ @0y@^LG@ su+@wز@M2r@⮘z@?{7h0G.?;?{v?2╿"H*?,?罬?ϖDT-?mĽ?i흿?w&?6Ž~?H@b?t[?b?BJzr?B̙@?4??8j?.ni?W+u? Nl*?h]є\Q @{IS[ @Rs,@â]z u? @.cF @{i*(@Kۥ@Bug @Og.״@ds|@]e߼R@絥\墼@‘p@"U@o㰼ܕ@zޡ.@ ѻal@B1.9"@<W;@:O@@Q ^O'?wh%@Z)@lŤ㭻ݍ?D{ޛr|?Qਿd@0$h?n%y?I1>~?lHc?дrPc?⨿ d?W樿g&?ђd?Q’?T礿]j| Y??)z?;{cus? 7?b/Խo?'?`%?T?0X(?ĠAZ?70Q;?/,KL?7[4Z~?2[̐?Ə- ?_BT?a?,壿# ?E_?k^?򘇾i?D ~?ړ @E``'?þ-D;@}<.X?0OD`@`H?(ek@̂\\?ِx?`X`?E<3C?̐eNq?#v&?)oI?䎿 v2Y?/% U?$vo?L?+ތD?* ު?պ?Qގ&?2mi?"G_? HO?8壾5>?.P?K ?knf? vn}̾? Q?b6(;c;?M兿?#0l?ݎ=m?!It?D?8E۵׹?ZՊBڬ?пu:=?Ⱦ"? վc?5^:?׏$?1HG?r_a?^{g˾?d? ԾO?PȊFms? =?Q1|W6?)u ?}"?0I? sC?¾Ƽ?el\;+?z侗?!ؾ?M?hM?C ??h?5"Y?=>5?񔍿v#x?Ή{ ;?㌿Zu.?vؾ?yژ_;?ŗھM3?+?F9?(> ?M+?-;?>iV?=z?vʒnCi?Hܗx!?C,"b?Ihl&)\?4%?R79?qR@?N>=Y?Q8H95?=C>?K GF?!H?Nܒ?`M?H.7K?H+@]1$߾"?ʟB7w?1A.?U:?-c?b[$ o?tH ?m?䝿$z ?*sZK#?f%6"?`ࢿHZ-Q?dAg).D?; 9?98eϲ?@c?`bA*Z?J꣪?ɢIi?/ pOڥ? Or?Ty?o¨U?5? (?t,t?Df#!"?E7(+g? 5ױ?%1>cp?G#:H%1?mRM@?3F@? si?ā ?=slؾ6?ćas?a"rϾ0?a7s?rdS10?h큿 |@?1cd[?u'J?6fP$H?I~x/%ᐸ?7b?V(i7?i1U? { 19?ņlͼ>V?~Wd=/?5҆3?xcgK>bq?$WH$?yπHU?oJS?@v 1?<=?) l-?N솿M?N;+ԫ?M5$ ?d@祤?)IN?Ď)m2?bξT?ݞ.ܺ?{W\jξ!?8ì?L1@?bf?=Ꞿ?! ?Oط?꓿ ?aL0^?za?mUf"?hfjdj{?%w㿾)?=iCy?٦ugr?¯0'?2)k竮?S#sC?2lAvN?fR S?x4&ġ?\z?C ބJ?`+T?|ZU?ì?e-?<墿v?"ˢ(?kt?gVK?~f?@*'muؖ?G,-?rN u=?㺾ނ?~l˳?TeG6cе?}㓤.Q?r^]L?[~@X?MPN}@?a9KyAョ?9aO}Ĵ?8'B?n%EP[?$PC>?jjmOչ?A^?x˳M2?oq?5?`¾rH<;?c¾A:=?P5CJ?ޔ]4>?_XIw?(=X8 r?B]>"QC?B(M>g4?E>A ?,>ׂ?6Yt2m?Wm$Mݷ?5I_3?H$M}?*,71S4ܖ?!52%ݼ?Aj$4i?#8%ʿ?'V2~?U ">N?R.?)>о>?1(>?߾5?ښ>L!ľ?XTc?hR>q9J?;F;?wC? "4Ox?X1].s? "B2?' k?k Z|??=}p?7?=^U?E>ku ?C>y\?9l>DO?MW>?A>@?{Dy?l-,/w`?YwQ?&*ݜh`? o?bU?@R"u?LRPʾ?`C$ 澅?Cvžs?)1߾A?91"ZS?Nlپ?g@ ?Y "Ͼh?j "d??"žM?2ڜ?)¾Y?*4O? A*`?!{lU ?n@NG?'/>O` 3?VHp?YWt?(F;]?RHSxI\?3a^?[5ufi? !叾 ?'"T~Y?V 0y]?q e@%?hBDth?/レ,g?þc?bƾ{? R ?_ѝ41g?|Gfq=x? q=+R?|~(=?&>o?I6?½Kb?d"l?mxO?>}=R?O= ޲?D>]"?>s@;~?.fVܘ?m`P?SL?B?B?&<˓?]꽗ƼF?fi?=?G;D&f?6==@?m< =?\= =$@?=>㻿OݢFǼΟeWƿL[ B'&zL[5H D[< J6s=NaʞU(Ԏ= f҂Y=g< (>/#XIpA=>:ZYRp>i"Z#>Ck ZO>[[+?ʿxw."˿-.ވϿŽſxg2U0Ŀڼ><5K@I8|">t.TF>^1u,>C`)7J>FTƂe>A4'o|>6Hp,a>HY=s>J*rXʫ>|8ʿ8 ѿKCҿhhʿ9@9Q YeĿ ÿ ;76Lr=<_5=ն=q= h>Ԥ-W >|'ni84>D4u?> ⌌8c>-x Í#X>Vہ8w>VD"c>j2jp>l>$NUŐ>&eRe>S9+ȿ!owGѿa]1Ͽ!ɿjľ ¿g<; [z8 >h<#eD>;㽓vA>ľ`·q`>#ɾGRe>a?|>nbwݵx> Ͼ_@>OӾEeJ>>nQM>Hy*>ؾ@S)ſ5)nipȿTt5̿-A[aD O<7yS ;E)Jǯë`=}#8Y8=ڄ2)=[č%ܤ=H>hTj>Gܓ$6>B%>0b;> nkք]Q>u~%qe>jݝJ>vSO>~Yo>* BAt>7=O>(sz 92V"%J˻♽?:?¿$zjø0-^է@<> -=zԽ+=|r3 z=m6|Œ5=:Cnw>=%㜊l>|s"S=jؘ7v>ur|$> efd/>-^=>̽snHwB>1ݽ Nb1>;!67M1.>;j@c2: > %f=,um?=X骿6 =$BüoC=ck ; =֚6=]<\Q#z'Цo=(<5mW>>򐓿MmG>ن"0FF>;ͨ;> y,<(>Ms_d?>\?5@0> )b#=[N=@JK==[}E:>Ur33;\J>|7=1=w$"O ==e! ;= տ]Gɿv]&rTɿCMտCKDӿQ)xȿt)ԏƿ=п tVͿvʽS(Ŀ!龟/h8L>ɿj(tĿZ׀ƻmJY5CPEIz9;as00\K&da߿Բ\Cxh޿'tCk{ 2Cg@*M) j@1ܿ+)]}r-ٿX,|}"k0jܿ[>l0տ꾋u|MпYoym ׿캾kӿ[dm̿-@VsIǿoT*.l_Ͽdv\a˿ߡO¿dXeb[x:[c\OٴB S53B?g𿶅)EgK)N@XQ[#ÖJT#2hPyῩQ]^-ۿuWD` ޿u|T׿KXGYZ?uZZ&+=dA$a+K1A,Rb@(U^*(vY1h6IƦ!p\<^! B uRe.X쾎(Կq[J{ܿcXFؿx`V8Oῠ֑3CϿN=I+[ȾrxZHr;'Ae3A۾p<(q"'{ ( 'v XZ/ :%~cQ𨻾W¢:m ~̐%ܿ!Tt$0߿W|ӿS)+׿TCw(.TqgWpŀA=@Fa'dI'Z$*jɾ{ԡ9|zo Z뾁վd "r Cžahy}RAT Q#Ѿٿmym3۾]ٿhUbݽ^U9:AJ(iiᅨ8Y`=O<^CD䙞=6JwD^ >迨C*=i$)}=Yd ,=濖y}=GlZH뾑۸=+T꾰< Nz;ݿD= ֿ$W=ۿNjE|)ӿ'G"οG:.<#ſ89<e˿Tۿ[>7WZY>Fn]>ؿIv>:e׿5-xT>+˹+OfP>{޿=>zտcm>ҿDVs>ڿy*>ڢտ > ͿR rV>ǿR :>ο]=NƿUB2=@>m\M>łNfm=տ̱\:>¿[|]>̿ ]>b̿_I n>[)?J>HP.L>˿-i>=ʿr^>/Es> ֻe> Iǿ&쾬>^ÿ6ĶmE>do>@Epϓ>69r>:C>.X7"m>21)!0>D4]l>񤡿|\??ék\?gq]IG?!OI?%-9 ?->_lA>?Hm6<>xs>'k̘> 30> ӛd}5]>2~Ǭ> 8c+T>3f$n>喿yҽ>xQAܽsX\>;[Zh@?4HS!?،VR?/=AE?S<Ad?'(k?gϐ^+w?$gK?҂MH?΄侥 ?jđ得3?>~m>qR>{u3>(Q>-'+מ>rý>@_M{LY!?yfO#?PtgYD?8!?~L#d=h ?ّM$?hdo&?Yj|?Oj Q?^fS1ܾ< ?nږ߾5 ?r}.R>)X >^a>>> xfg>3~p g>eu"|>lZ%>֠>!8W?57QT?!6=E?< z>!?T!#!-? 7#?S8Zt Ğ?j#h ?_& ؾ>aq<,پ?Ao}>*6=>0\bYֿ>GpYvc>o7FҖ>B>mc$Ch>ŁU@nJ6}>=kT->'U>U!?;^ )>_(" ?" >JA; $>U6׾ >P׾ a>Wנ>C $ڢ>oן^u>' fuZY>|&FM؈>KHEr>ln_a/>,:ZBM> V;ē> >ξ툿\>T@>xm>@ʾ 1lBM>LʾQ":>.TS>{x9u>>˾V7֛>FϾ'>4꾹;>Qムwh>ԾpB> پվ̮>]>_?׾ϼ>Kȩ>W8)_>#澸nct>5b,>qaDO>3/>?>v?ܷ?'>"t?'?>u??>Dus?M?M>&p?~?q>ľr??~>Su?\?3>Ys?ia?z>$Sv?"?2j]>v??j_>u??a>ӳs?U?@>u? ?4B>;r?ٷ?Gr@>*v?/? ">Rt?^?#>ޫs?h?['>Mq?C?>Uo?!?̟>\q?Fq?1?kڅ>v? ?͈>v?k?{>Itw?ݴ?a@>?w?v??Qu>v?ΰ?QZ>Z=v?{?[>v?t??>2v?7?p?>$u?®?'>)s??$>'jt?9?[ >Xq??>q??>}w?T?Bb>w?bW?v>ڽx?c?{y>%x?{ɯ?Nv>!nv?燭?Uv>҂v??eD>#t??2n_>u?5?eH>4t?Kҭ?(9]>Mu?v?ą>x? =?f >x??׏>ex?_?>x?~?5> y?&8?`>Ax?נ?Ex>Gw?"?y>Fw?M?f/w>Dx?t?֥`>[v?g?zG>?v?? |F>R'w?v͟?p^>$x?Ӡ?Q+,>u?? +.>r?a?,->Us?唧?+>p?HH?>q?? >er?P?1>|x?Ɯ?9>Uux?֚?>vw?T?>ux?_?~y>x?ș?{>y?f?pBc>Xy??`>x?H?H>Ux?z? J>x?H^?(+>7-w?Y?e+>v?jt? M?>bu?bƖ?W>vw?Ȕ?v>Gt?Ɨ?>`v?fЕ?!>x??>Cv?(?(o>Ow?FV? @i>x??!P>x?Ɓ?[U>v?Ȑ?7>}u?,?1>x&w?6?>tt?B?>s?ˋ?OZ>r?K"?zo8>#vq?ZՌ?2><0o?Ј?>Ji??(:>azk??3_>rjl? ?d>[d?X?f<>-vc?CT?y{>Aha?:?(3?>Z???y>X?d?i>q[?2|?m>R?|?DB>΃Q?~?=>oO?2Bw?KE>H?x?e>Z%F? w?r>I?|m?2(y>Ce??g9n?eH>=?Ap?s>;?جd?FfK>0q3?f?˳>Y@0?c?>_5?t"[?V>L,?[?<^N>`)?]?s>=%?}&S?Q>?Pj?/R?#)>#?9H?>?EI?V>L?@K?n>5?.@?\>b?B?&>?>?T>XL ?6?>>`8?d>Ȁ> ;?$>>T$1?]n>x">4?`->dL>k/?(G>>(?p2>?>: +?q>=>E.?$>#>HR&?t>ބ>)?>8h>jF#?>W>g??z>$W?v?8o>1O?=?F">?.?(z>M?Q>>t8?L> ou>;?>ㄲ>?s?^>??]>[??(>]]??>#?]>"W>??%>?B>$>Ȳ?0 ?>?Wt ?8)?J?8>?+ ?(>FJ?F?> ??u>?3?>zJB?h?>9?{?>D?r?> ;? ?ָ>?E?HG?L)>O?NM?8z>N?^?>L?v?{>pV?]?3>IX?o?͛>SZ?4?Mʴ>bDb?G?ߧ>Wd?"?v>0_?<9?Z>hi?"F?MB>Qj?9?>@?r?>Ŝ?yW?>e??>*(?w?>'??GP>0??>d1?ܿ?a>D?[I?>+C?d ?pw>OM?,?H3>M???aU>)V?ʺ?>{E?30?E>sD?c?> N?A?Q>TN??f9>ubX?N?.>rW??t>`?,?a>{a?~:?_>^Rj??M>ai?>޹?>:q?Է?=>q?~'?;>.J??>c;H??>o*R??ll>[?SK?>Y??>+c?ͤ?W>k3d?|͵?>Zm?|?>9k?? >Hs??>'u?^.?״J?(0>4? T?G~>7+?PV?>\1?MZ^?ކ>0:?g]?;>8?_f?cj>?=?fT?>7?l5J?>Kq2?qr??>K/c?|?t>Z?ם?:>!^?? >_?9%?v>WW?i?>bFh?P?_>3>?W|?S>5:??> ?? ?;>9?1?Ɋ>6?H?e>;=?Z?d>:/2?Ε?>u-?Å? > +?;p?m>3? ?>t0?|Ë?T>5??>>q9??)v>1:?h?Q>x8???>=?~?ݾ>=?9?L>>?7?q>oB??߷>%LB?ߎ?>B?S}?,>}=? s?د>7? |?޼>0c7?p? >=?>? s>YB?^?M>C?Wv?a>& L?y|?;>[J?<=?Z>>"J??>ـS?H?Wk>Q?G?s<>;U?!?8>7K?.?L>D?ɛ?V>4L??>E?W?Ӯ>eC??>I?f?Q'>eU?r?>PR??t>|vP?H?l>q3???S>:8?a?i?>P@?px?>H?$$?i>>:G?i?c>#N?d?(> O?P?6>X??<>V??>^_?y?>>`?Q?>]9e?]?>9d??8k>i?I?>Sp?ݓ? >Oj?䄗?>'0x?C?>`I?'?>P?5F?p>!\?(?3Q> Z?d?>c?a? >Tf?n? i>?m?Z?>i?ں?g>vo??dK>Et?!?2>{}??(>9Ow??>O?,?e?7+>01?՝w?u >0?sk?A>Q*?yns?>D)?Ng?N>}w`?Z7?> `^??L? ܮ>i? ?i>;ik?|3?u>t?4?F>4_q?n1?>xy?~C?k>yh}?1'?>փ?M?6&>s?n?>C??\>a7?W˂?>:*?^? > 1?-e?>7?1k?>=?Hn?$>AF?PTn?ڨ>*@?>e?:>|I?$Lf?ܫ>:nC?~]?>N?vut?>W?lz?Ǧ>Y?\Ts?~f>EQ?ql?Ѫ> d?ȩv?׍>b?ng}?惪>ƨm?<?>So?6z?;ҳ>Ҵ?~?o>X;x?x?Z>ք?w?>v??H>rx?~?>5?}??>d?v?4>_?s9u?>e?c2?Y/?A?{7?&'?2˻?>c?*?X.?д@?n,?Cf?H4?;5? ?r0?5?'?)?;??g7;?.?,?;?E?6?Fc5?v?@?q6.?:'?ȑ8?<5??+B?8.?e?U}.?:?Ʋ?)>?x??8A>%p??T>,z?ů?=9>$3r?2ò?\>f?K?;>Yh?U?W>{?ͣ?>s??a>G|?:e?>M&t?GC?R>|j??W>&|?8?vE> +t?"?.V>={??g>,rs?? >0k?ᷢ?8>Nj??\>y??>"Dr?ɝ?)A>dBw??\>cp??>$h?_?H>j?/?>]??)>W_?|?>&T??q?T4V??g>a?X?W>{`?Ĩ?k?IX?@??W?}?g>6b?H ?>a??s?VY?^[?{??Y?E?>ea?KN?>${_?ב?aO> X??>@V??'?O?Գ?s?AM? ? ? J?Wʴ??G?f?)?Q?? ) ?ZR?٥?i?L?Jd?b?5-M???)S?^?5?R??!?#M??np ?uL?J?Mw?nQ?ݨ?v>QO?>@?=+ ?hK?q۱??D?v??>?#?!?Y@?? ?pB?#??MG?z??)?H?o"?M?D?q??C?)V?.?H?^z?f_?G???=D?!?6?C??O?F?u?~?S!C?},?&?q>?? K'?cv???_/?9??lG1?Uc:?5?@$?ku???6sJ? ?k?eJ?7$??H?!??w?,^?f>>Š?S??I?\?O>W6??[??,?Z?/v ?ղ?b?լ??a??"t?m\?y?n۬?-\?)}? e?Oc?| ?&#?ac?m]?i?,n\??a?y[???Þc?7 ? v?MIc?^ ??$[?G?X?_CZ?,0??Ic?F?zq?b?u??\Y? ?j[?X? ??,?g?\>?^f?yu>?@p?>i2?n?,>)?od?[>??al?>?9k?#>?i?Z>??$Lt?M>?.r?>>?nq?>?lj?>.:?Zj?>> ?)p?Ih>U?k?u>?Hl?2> ?CLr?|3>h?tu?1>a?,,l?3>&V?lk?>?)u?W>xw?ll?S>K?l?Ɋ>?.u?>?Hv?X>Ŧ?Zx?z>?z?>"? z?z>&?}?>?_z?\\>׳?'v?>7?y}? >1?qz?f>e?ML}?6>X?[~? >l?zD?c>??Q]>?<?n>?*?}fY>?d?>ȟ?#~?IU>/??OX~>|8?h?}gp>A?,?0>xM?x?ɷ=}??@m=Y? ƒ?޼">Թ??U`=?^ف?a>? ?zL>_Ɲ?x?1>%K??N>$K?x?Ɔ=??X1?m1?e?,'??? 0??'?i(?"??Z?>3?\M?>?|V?#!>)u?`?U>m?h?>p?\?P>`?.Gc?5>??m?ȫ>T=?qvh?:ʢ>~?~\n?>I?#?_?8?{[,? ?} ??Ϳ?C?6N(??;?i6???M ??>.?51?qf>@?9?>;M?}H?ͳ>?oQ?@>?)B?>ΐ?K?>?W?»>?l]?r>i?R?ű> ?\DX?2 >?ȼc?Ֆ>~?Qj?z>ݷ?^? >8?uf?vAu>1? G?>n? =?>j?WA?>y>?R?>g?)F?W>f?~K?P>?&L?>)>?&^?l>n?W?3܆>S?W?e>6 ?P?a>?S!?K??M?4 ??=?v>???U?/?n>?" ?5>?\%?>$?(?%>?,4?0M>v?A8?~>[R?L.?>;?O*?;>?B?&>K?&=?>?n8?W>k?$W3?@̣>?BK?Zj>S?fUF?>?{??˞o>?;?A>2??#>? /?HŌ>ں?$"(?"ܠ>W?.%?(>:??U>??^!?±>9?n2?v>??G>g??ey>c??G>h0y?t?>p? u?sֻ>=q?dp?~>X~?C`?y>I1|?gj?(:>t?J]?'>(s?_f?>Z?>L?`W??'V??pv?I?~?*ru?S?+?_'?6?@]+?b;? B? ?ķx?5??&&?=w?s9@?*?;?-?t80?y?W9,?+?gf?{Yp? >k g?ij?)>[?k??Q>5;]?d?l>h?a?@>?Dj?gX?f>a^?[?{>K`?S?I> j?E?6 ? 0_?ړJ?>U^?MA?0?j?_O?|?n? 2? ?|k?!~U?i^?7>UL?^?^>J|N?J}X?v>#X?iV? >9Z?N?>Q?P?>dT?I?>VY?E?t>5E?W?>H?ZWR? >g??>7P?qY>A?,L?>&K?J?^+>M?LC?>D?BD?R>{G?T?a!:??fQ?x>?H>C!L?~2?A? I?v7?s>I%\?Ri)??+X?n1? ?TT? #?,*?O?2+? ? z`?j?.?RY??T?ٷB?Y,?x>??׮1?>P?9?O;&?0D> O?C?9 ?H?)$??kI?T?q ?1A?i?>;S?A?K?:?E?>I)6?2?:>O=?.;?YN>C0?)?D>2,?@6?b>R'?Q-?(>?80X?h\>2{?eQ?_>I?L?Jb>?&E? d>T?|]?Z>?Md?6Z>zr?1e?`>p?m?}i>$?4l?x[> @?t?e>t?u?F^>?UBq?e0>h_?|?>?͓x?{>?.v?ǵ>?Wu?>? ?IF>H?d~?>0?W_y?>b?w?m>?́?(>_??!> ?R=}?V>3?{?> }?l)?Zп>e6?z.?>"?@A?E#k> X?iI?W8k>q?ז>?qap>Cl?kF?Ps>(?!O6?פq>|?T?q>D?scW?רe>w?``?|>b!?b? o>?ܢR?@{>?$L^?M>D?}c?b>*?l?>>?n?sY}>b?x?ࠍ>?{?z>?=v?`>AH?Zj?Z>Q?r?e>;?E'~?ܟ>lt?Z?z>o?q?so>?J!y?(f> ?@~?ům>tH??W`x>??|>iK?8s?+>e?6-?Y>??>?Y?S>?ۄ?ԫ>?~?>T? o?x >Y? ?/>,? ?.>P?d?շ>I?F?X>,?귊?T;>q}?a?_>H?$?z>6&?ڇ?$>?}??ot>X?d?v!>Z?O4?8>?h?'ۅ>R?#?>x?19?Eד>? p?>@?r(?O>Q??z>?z|?m>‹?Nj?6Ŗ>_???>QX?Sď?e>5u?rˎ?>??I>"?2?> ??:>c.?㿐?ߤ>[?ː?@u>??ZϷ>]{???8>?EJ?^>L?9?q>j?ʰ-?|>W?y?.>?]?S8>?"!?R>f?'?>P?MΒ?M>™??h>%??Z>?ph?L>"??>h?[?\>T?w?d>?? >?z?2>??>]??=>$???J>??T>??.̵>?F?>r?Ý?º>tS?F?̾>?Ᵽ?4>?Ȃ?B,>?Ӡ?8>?l>?|8>?S?>?r?>PD??+a>?*p?>(@?ˍ?>?_?>?s?>{b?`f>ϯ> e? 9>>Y?>m>?>>Ki?>e>5?=>`>?>\x>1p?pT>1>Q?>&9>r?;>>?,>+??>N?(?V.>>?h?u>?C?ǿ>(?u?>\??u>&?-?>E4(??>Q;??\>O1?j?L>:/1?v?>:??>P?Q?k>VE?g9?>E?׶?BL>P?)`?{>e??a>z[?\?Ǔ>Y.\??>f?1?Ro>l??>m?U?,n>8xg?_?2g>hn?I?G>Rg??[eD>m?"?+}>ZP?v?L>[???Q>N??(u>i\?w?]>D??%>a9???2T> C?b?RX>q^7?]?ᐆ>0??>'?x?JZ>-?~?[>$??BI>?q!?o]>?$I?d_> ?l?>X?h?->V>6?u>???Mj>>z?d>%??p>2l>P? v>\.>Z? q>%>p?;x>ή>D?L5> Շ>l?>uQ>`?>B}>?>3=>w?%>\f??c%>hl??>˄d?h?Y>2j?M??-=BYX?,b?<=EK?ڶ?#>GqZ?6)?$>S6???=g??!?q=b4??$>A?Ir?$>#?+,? %>,?dW?=J ?U?/+=8S*??$>?t?Z%>z ??W=b? ?K=?}?O(>r>2j?o,>~>v?L =)o>5k?=y>X?2> >̗?K:>2>Y?2=>?=wҊ>!?06>:di>_?4>Y'>?|=ԑS>LX?ܺ>mT??>UM??>yS??J>K??>(^??F>(g??.W>V\?7?>Kf?Y?T>dn? ?M>˔u?%?.F>m?g?p> t?_W?>3S??=@>L??>(T??>ʹK?1P?{o>0e?S?B>$\?K?ȫ>:e?K?HO>\?6?R->(\m?"?F>Ot?v?>gm??>vL?䣍?M>j_U?R?8>V?lȈ?x>M?iA? > ^?-?3?>4??7>F*?x ? g>u1?Md?>Q$?w?v>%?b?> S=?j?>;F?@ ?٧ ?9??>B?I> ?;Q?J/?+S?]?D???UPN?> ?Z?\>"?a?>$?Jr?>75?Vj?W?>,?m?ue>v6?Nd?Lv>H,?_?u>-G?|?R>8>?? >!J?@x?.s>k???u>O?]?W>AX?d?}>2Q??ͯ>Y?셋?Ѷ>mY`?? >+h??BŪ>vAb??ä>Tj?FY?>o?|(?(M> >D/?zG>+>sy'?a>>-?>KP>y+"? %>>u"?Lj?v>E7?O>>5@?a> ?О5?>S>e>?=>9 ?J?#>?nV?.> S$?H? > K?vS?a>*$?]`?5>E-?Yj?aٻ>O7?1]?\>o".?g?ш>yB7?d~?>K?Bt?>@?QA{?u >SK?pq?>@?ھ?8>R?X2?>Z?1??8>dS??w>[?9 ?Wў>c?`? 1>k?x? >gd??)׌>YRl?k?ە>jq?Z7?>br?['?V1>/>.?(ҹ>d> "?>>W5?B>>=?<> ?G?>x?eR?\>$?[?f>:-?]d?I>~6?<o?C>8@?x?\Z>K?}?>iS??8>\? ?.>%d? ?|>l?`?=v>r?|?ɢ>5}v???%>x?*?kʳ>Wz?[?\>{??>؉y?ģ?>K~?à?a>}|?h?q> ?"?>4 ~?B?8N>?z?X>I??>܃?z?>r?zi?p>}?|a?ä>E?ݮ?>`?n?>T?Cv?k>{?2:?>x?څ?>$z?{α?$>}?q?܎>fv?l?j>9Ot?ε?Wɡ>x?K?g|>4x?y?h>w?W?=>[z??tp>Ox?K?Qa>9w?q?jU>u?T?Qg>}w?ũ?p>$x?Y/?z>1x?z٪?~>y??>y?y?i>{?x:?ۓ>Ts{??at>}??>Sz?l?><{?ͥ?nj>r|?F?>]O~??B4>1|??a֔>diz??b>'z?,?>&y??>y?,?O>z?n{? ?$>}?{??p>y?3?ܾ>\x?V??>A%{??~>Zz??S>|??I>~?E?.>?ߡ?Ms> ~?_?>k"??Q>?"?a>\??.>}??=<>?=y~>G?a=xf>6?=n?Y?=en??E=k??=a??:v=jXe??0=Q<^?K?Y'=auh?g?NV=WU??=dI?d?p=9R??hq=F?>?ӯ=W=>?^?q= 3??4~L=;??>(=L"1?O?lO=!??&=%?Q?<?B?Ȏ=(??^h=@?5?ysO=X???<= ?? ẘt>D?A3=&> ?Y=>?E>?r&>M?Vd=> ~?H$m>d1?k=IƜ>c7?4=c>^:?=>eE?ƒ<>B@?-O=>%J?=W>vR?/x>-Z?=j?R?We=?] a?iY3<;3 ?Y?a~:>]l?ݼ=+?b?=`?r?2%=&?.i?Pq?:?ޖ=hn?]d?=ii??dH=˲q?6g?Х gX?A_?#<4>jn?Y$(>|Xz?i;!?q?P?,?K뵼d?(x?#F ?1?=;??޿e=}n? i?>Om?1?x=j?>C?n;=kj?f?0=ol??x=¶h?Q?=Of?b?d=L}m?ɬ?n=-j?`?|=Fn?m?n=\i?,?w=Z??o= }b? ?Tf=V?Ƿ?%Ҏ=K_?g?`=P??S0=D?J?3?;#$?[?u ?gf?.h>!?y? ??/!?h?V K,+>d?Yʄ%>Ȇ?!ýHG>݂?[6>?d>?dV?\?~>˿?ѽ?q8?i U?SӾ?ɿٻ?@?l6B> ø?0N?W?*n>C?2 7Q?*X?۽?"?Դ?:?¬24>?e:1?3f?7~>؏?k??̣?7??=𽬵?T?95>?4'5?ŕ?.>J?s?U.?xb'??C?!?Q[q>S?mƽV ?1?< X=>Kۑ?4??mNg??O ?Ȁ}?JQL> ? ?_?4գ? ?wMF??^G>:-?Ѯ?:{,??:pE?H?S ֎-?׻?s:K!??0TȽn? ?%Y1?\_?J-? ?/ -???M?x½??9ۈ>,?.?C'-?o? tI7#??n[!?!?E.?_x?Vl.? ?4b5?&ѭ?O.5?K? =?Ҭ?q:%a=??Nt|=?G?8Ƽ5?Y?}5?P?*m5?#?eH>????r^5?!?*t& 6?0?F,G??%??>?n?{ɼ88?r? *&57??aH}A?}i?qd@?W?喼LF??p?~F?g?O?Q?}oN?j?֤;@N??ŻԑF?b?GG?ನ?ҊO?v ?2ݎ P??[G?׈?n3H? ?{3H??ZuP?E?˖GQ??I?o5?z*I?R?;Q?C?AtQ??|e;U??NY<U?zP?<}tZ?,?F:DV?i?#:"W?\?ʿ<`[?Qæ?ſ;?L5;6>/?5֒8|>& :?6]6>5'?<E>}*?=c>PF?Pؼ"÷>[R?mk>SD?x9>O?'$ֹ>^?Oq#>l?;:HB>[?Q(>i?- >`{?zW*f>c?V5>Sx?֒I7>oՃ?c>|?2G9]>j?W e>N?u~>w?>?]>+?;`eT>U?\t>w"?׆>璪?a[~>߱?Sg>ͫ?O>?u>?=a>?z > ?Gp_>?J>?γ>&?-]A> ?V >?dҥ>3?ɽ%>?I$>!?/ u>H?pE4c>O?x-S>>6? w}v>K?)>9?EG>t?q.> ?Q=>0?2ڀ>o?_k>$?+$Ѝ>'?2yVn>`?F=5>? y9>8I?B'Y>ڐHA?[">2?3>>ԦU?zH3>X&b?i>M? >Y?J쑾>2s?BK>"?=>Nm?N^>@?>?@ۗF>_"?٠À>Z?+l<>i?Ur >o? >?QG >"ؙ?ľBu>?pʾ>y?T>C%? C>r?,Ǿ>?ɾ> Q?!eq>?a=ز>y?ϼ >??¾)>?@.4U>-?Zm>?\RU>7?4ُ>Q\?$r~kU>mY?,򂾢v>a?Ȝ{b9>?~^>M?4>K?q@V +>?6=y?W@wB=m?32 =y8?lbEW=n?3j1mm>A ?ֽq7>!> W=>oG?\[>x>^>>_>hl ?dk#>? l__>ƿ?毘yմ>>(?^+s>A-?=x6>=?V>9?X> I?i> Q>>rl=?>}hG? >XO ?E>sU?y?eD?qX?>pR=2??Ly"?>T|T?>y?5>JE? E>GB&?0>1q?G"> k/ ?????i-(?X>C7>`>ٳA!?>U>A>>X ??= ?N>>ξ{>>̾lX>[6?˾.>>?Vb#??ҳO0/?*I)?_%?*?7@M0!?Š(?݌r)??u&?\?'c=?[?>m+?+?/;=?,?y){^?`m?3??>H.?Nj ?0?H?>/#?fʾ>3I5?ɾG>K:?ވ\C '?:?Kp"?]`K?Y&?7K?v I"?J?Lj8)?&9?Hnާ*?U=W?!~žd>;[?NV$?2\?,G$!?'l?nwT ?]m?dE?] k?V`cw#?O [?f'?{^?l5?@`?%#?o?F4^?7q?z$.d?]c?l\O ?zf?R[?Ou?ov ?wx?w)?9i?@˾>|?cӾ-> {?PRn?x}?Jo?^7?1K?I>??? 1>?sؾ>l?>޾ˌ>6?FR! ??dCQ??R,>^?7C>Lݏ?a%?b?&a ? ?3=M?Mm?v$s>m?N/4n>?:$I>?I\>Su?@>?FE>檠?3>揘?pK>Ζ?,WF>-i?AW~?>њ?G>9 ?i\>4ڠ?6XJ>c?qh=e>W?n>6w?6q>2?$s?>&'?71>l?Da%]>b?Ӫ>:?v>)7?>? }m>x?9ntw>hk?)㾸K>q? NV> П?Sd>?Q> ?k%>m?y >?'F>u?G9>5?}%>W?6;>ô?r%,>^?ٷ9>I?U;,>3?ή>yӺ?>\?yϭ> ?ݾe>gm?5ku>r?R2>?0k_9>ե?TRJyM>?~܁I!>]?́2a>?Ln\b?O>&׍>Yp?ߢ<_>)?R&m>k?7ߎ>?*>R?t>@ ?^w>z?6Xؾ.>"?Ҿz>w?k^2>?RE >Qغ?j=y?dQ"=pݳ?ҁ)=5z?8=I~?%;?&%;>%?;9=;?Q&/z>?5iH>bR?g=AQ>K?w> )?󾐰(>N?];V>?ØǾ/>B?JP^#=z}?,jJ>y?ʾW=% ?죤=?P˾=RJ?v=9?M_?[g*е?yF󽋏?/$H g?S9xV[?,?7똼g?{s<0?N˾MhJ,?֎?>>?'?X>g?h?>x?E?iR>?!?="?)?M>$?.?BF={?W3'?eI=?R4?=cx?;?FW?6?%>1?;?W=?C?&0>z?I?>q?fA?Ħ=?ԹN?-l= @RYQ?Y>>\b@ZV?>4 @_?K> @c?&>;?[?La=?i?T>>9@l?%]>L@q?8>#@rz?yn> @!a?J>@v?>??J)>E@2$?gǁ> @$?Yq>H@|?D>@?]>GS??cK>r6??9>"?2j?>o??+>I3??>Mq?l?>ؕ?ϙ?w>?C|?ma>??W>:@?֣?>Xo??ϱ>%?\?>1!?W?܊>͍?U?q>H?f?>G??Z>L?>?+>c?%?>%?MN?>U??p>)D?9ͪ?6>_?~b?>?Mi?>g??y>4??ߕ>??>;)??xU>r??5>?4h?n>~?tU?:m>?i?Գ>)*?3O?J>+??*>6?h?P>??S>|?Zi? ĝ>?ܚ?A>J?$D?싋>9?bj?ta> ? ?Ǐ> ??>?K?oQ>@L?,?u>Z?ꍖ?>m?Ћ? >E?羈?0> ?/?Y>??B_>o???^j>? }?ӈ>?Ʌ?G\>~{?n2? a;?j??þ<,?=?ۆ?iJ?k?1?d\?E%?7}?V?;?&L?Y(=q?X?Tr=:? c?|A?֞?Ѓ=?;? ?=mx?E?> \?;:?7V>??08>p? ?|9>?a?>/ ?Þ? e>?K?x>?3ء?l>??K>R? ??>?$%?>"]?i)?>A?Ƞ?W>?nA?h>6?H?}>ZI?3?r>t ?2?a>%?4?P>Z??59>M?C?!I>P?lL?PU>?Ց?%o>??}?+>;?C?-> s?':?޳;>s?S?6=??c >???(>?O?=;D?B?SG>O??Q=?G2?@0=? ?9>ٍ??? >]?ߑ?}=O?[?Tq=D?㌖?w=~?C?I&>??Ss;>a?S?>HG??">?|?3>A??hi>(,?1Ő? =w??=??T=rL??sN=l??/=6?!ԃ?W^=i?$l?ό=)??q;w?]?y<-?n?=??!=@?ֆ?}=$?h?K]= y?Wԋ?4=??T?LX?-?>x?W?1>a?l?>Đ??գ>d?? >4ʓ?܄?|>8?,?.>Hv? ?a~>,9?z?F>dL?[?->d?4?>4??>??ɍ>[܃?Z ?>?.?i>~?Ț?MѾ>B ??f>z?l0?ը>?w?\X? >v l?:?V>m?ʢ?>Du?$?+>)i?Ң?>6*r?$?i>b?|?H>U?q?>|T?.?a>ya?e?p>gU?q?/>n_?{O?>M?9?>C???a+>@E?9 ?|>L? ?v>\C??3/>gL?'&?*_>(=?g?>٥;?٨?>6?>@?s>r4?Hv?i>;??>4??B>Y/??>O.?F?{>#.*??3>)?G?>:]/?Z?>*?lZ?e>>%??(>U$? ?H >"?\?mf>!#!?,?@>&? ?2Ǿ>("??y>L?nQ?ѽ>-?G&?z>`?@x~?>e?o?E>&?ɇ?ժ>??m>?=o?@>M?ҋp?>K?`?S>?,`?>:?q?sՒ>?a?i>ځ?!P?C>@?]Q?x>?A?q>:Z?A?ڀ>C?dR?!n> q?k[B? S>]?fu4?\> 5?4?Ҩk>?+'?>?'?U>@? 5?9>^?'?;>߰?h?N^>3?!?^n> ?8"?E>?e}?>m?? _>?ّ?`t>7?Z?]>4v?.?b>_?\?>4r?W?<$>&{?E?n>d??B">| n?il?e> %`??>4Pi?>'?L>+?ѕ?}p> '??Ϡ>Y1?_?>-?8?>g6??>/?R?>4#?Zڈ?d_>?;?">ٶ)??4 >5 &?<Ђ?j>I?l@u?_x>xM?tf?3]>}$?Wx?ZM>Y"?j?1>~?=W?*?>g?}H?!>"?v\?@>^{#?N?b= ?;?>Z ?Š-?=$?@?V=K%?J3?P-_=???<> =?˾?>I?͠?>6F? ?W>4?Ș?>P?g&?4>4T?+?Ag>F[?:?sյ>34?M!?>*;9??݊>3/S-?,e?eI>Ӕ0?ڊ?c>,5??7+> a8?ϋ?-MH>r?*?1r?>{+?8}?O,>H3?x?=4?l%?>cC+?dXX?K<=-*?We? =4?a?m-=3?!Sm?=@-?>?K1<`,?JoK?2=x_7?fI?PHW6?6U?>;??dD?8>TC?r͈?d >r:M?U?= T?خ?(>UW??}=R??c^>F?l? _>`N?Cy?=IU?M]~?(<R?O?Bk=|`?V?ˡ|?b?G?m>.$_?Ϊ? 5>*m?2?/a>Di??F>[??7cg>se?$?L"t>bk??8E=-g??=v??@=Jr?ܸ?@=b}u?;{?m3]p?]?"W;?*?~gp|??r<?8?A=pz? ?: >f?d?q>?Ԍ?)>u?Wa?1>D{?G?L>m??^b?Ӝ?T>ܡ?"?Y$>?;?L?e? c`>]?e?E=9?&?:g >M?6ϛ?>-C??쿇>aX?_?Ĉ>|p?Ӕ? Z> w? G?o>j?N?@>;*$?F?t<}?]??ؙ?m)^?Jo?.:a??=?2:?Qӎs7?&?yq< ?[?ca?^?xh$#? &?{=Vڐ?Ch?.>?YAa?VcK?;)t?^? Ak?U=?Y t?\NN?Yk?д?`?񓡾o?pa?{?Fu?@ƽ$m?@m?skE?o?q?f?.-4?b?8Z?Y?RmH?V?(B?+XK?׼xS#?U?\þA?.T?ξp?kG?W߾c?(7G?1$m?F?6fԾu?aU??[U?ξ5?T?a̾+k?hG?6|>?mG??S?,˕?EG? ߾s?E?־&?T?ž_?wb??WV?ș?c?;؁@?@U?Xb?G?LfY?F?'ʾmk?RY?B$i*x?u6X? 1f?@J?_Js?pI?}?xe?iYv`?ԁ[?=03[?GL?giP?M?[g,^?c?SP?)X?o,?l)?KZ7+?L?Y%?#?$I3?:!)?W2?x? uc5?5?0?`?N[?3%?&~?? N?z$?k?3?As??@sJ?n?O>5B?sq ?>a?YK ?Lp>?C?8>?3>?Kx}>? ?;*?>n?3 ?t3>?;u?n >7??Mq=j?W?yD>O ?k?`=F%?u'?c<} ?}<"?ܘ=+^-?4?`6???GcB?;LB? ~B?ٟL?%gn? 4?=1?P>8>~?u>k=??AD>>??{D>? ?K< ?0?9=_?ѵ?M?[> d=8?Bf#?^Ž6??@3Q? ?Ԑ?!??1 ?1?V@?׎K?d2%?B1@?*`?[H>/> ?{?eG(?@{?R ?u?/)j?\ n?_Eq?l?xh_?W?)aZ?I?@?5G?ﰾ6?cG?Eþ?:W?ޣeJ?6?޾ӧ?7?v?%?kG?h:'?Q6?+&"?u"?ʧ5?̾M?J9?$?9?d=R?*?`Uҫ?+?^%R? ;?8Y?:?;17?ct-?5͜?-? a?;?,?L:?~?}J.??-?f|?f:?{cӾF?ܲ:?Uoྎx?,?羱?m-?T`?.:? Vn?#&;?6Ծ"[?)?$i?+?Ծ]fI?E{9?\jT?W:?C?-M$?uN?\~'?G᡾?=?E1?m)VX:? ?uM[?ы4?ӝ?? ?ֈ?,?c,??d5?4:??sѾP?(>N=6?>=?"> c=Y?L>>F?(> ,.UK?o><; ?> ?> +q?Aa>ٽ3?']>0L?7>Et?>qI1?a>l ?76>>?>Y n??_?" ?Jξ?">=že?>x޾,P? ? &s ??ds?,>>?>1n??NU?kz? ?-? ?pL?j?ɔ?/??Le綱?(?INE?N1 ?+?X?*X?k?yе? ?7{!?pv ?s??j A:?r?ɏ ?;?c}??Bu??a??r??N9?]? X??;6f?x?AjT? ?.ܾ%.c?( ?n)I@??ulK?J?^=? ?=EJH??L0? ?37?V? n}/?>T5?2>[$?#?&)??/@޽%2#?>l˚(?>yo?>w=cn?t>;<?P>?A,=]?y>2ɍ?7>K>.?>UB>ޱ?>&>U?&>m=&E?G>&>=?A>DMh>T?m(D>d%=h?wT>Z ?}>$ݽ)?F>/m?6>mK?&>.?Fʪ>YW+?F>5Ӿn^?<>κ?>V?>| ?' >,Rͷ?t>]?>w!-? >w$F?>$#?n>#d?6># yɆ?KD>">P?_>|Y*ɜ?~>-%$}?>vn?> N5y?X>uj?>T_?>R?a>뾐N?]>n][?~> E?|>!ξm{;? >Ew8?P>80B?\>Dݾ.?1>ooKt4?p>0a,?ի>?B1?ˢ> #?>$ٽ(?Ւ>."?ꘕ>A 9'?>`M^s?K>c+? ?:>lU ?F>?>>??Yf>iQ= ?}M>Z=q?e%>=!? l>̙T>V$?:4>@>"O? ><3NG?ȭ>S=.9?I>h`=ϖ{?_؊>7ar?j>u&k?]>0*]?>&U?C> 74I?G>N?ȏ>A?k>۷ D3?>ʾq=?;>,?0>zYپ۩6?>!x(?>-?>fn!?lHg>7&?Mv>JY?ip>*(?#?䓃>1h5:?<>@Ch?C\S> sq=?B>.3?bZ>B]Խ? >?$>@~> ?: >;=?(>;<0?=Z=f?=|ٸM$?=!> ?H=>u?J>iG s?Q>BNJ}?K >JR;j?%>Oic?b>9t~T?8l>.^-K?=>8aZ?+,>-DGE?Di>#)6?8`>t3:?~;>c-?*?5>9"[!?U>K+?^>a?k)6>fI?W:>?M>>0ٰ5+?L>f';?#> ?$w/>oܾ ?,>Վ# ?T>UZ??ܖ>V|>]=mJ ?g=~Q= ?w=->=N)@>v="ͽ% ?9=->VdGDe~=7>!=Z>Eż:>?@mk#>>R >v>讀<ݽHr.?!>3`??>] >@3?7>*DK?c>QM!=?O=gVKX?0=ZSs?=-\'?c=Y6?"=4'A?V>>?B>,ٓ?- >8O ?%>5?k$>F>B>>k$>K K>v>9S>>GȾ>>+*?>a(> ҾA> >7>=`>=B}M.>=>8A=$>=Z@u[>t2=>q=S>(<ь>օ1Me4؛>0;׭>=׼>mb=>"$=e>ʽ1=Qm>ô(=^>b=ob>x)=}S>=Cgs>c=]>DB>>1Ŷ>u>(H>`%>6>k">n м>$>!>B/>_%Ô>L[2>Yɲ>8>v>g>zپ>y,> >* > d^>a>$>s=!s>$ > `>^=e]Vc>:=i^>-=M>^=šgD;>g<=O %o>*%o;a֎Z>\E# '>;ͽL^>bv{-D7>=ɽ8<>+%pV=S=\^<#y=l =7Ɏ>H=l;> >% ]]w1>02=Dp3#>A>Eah|>%>Li>8>d:ct>!9>5O>nL>>>9Y>VA>(2H>D>DT=U>^,=L{W>-o9>W=>m)>8/>=vO>ԛ=\@>[4T>aU>7. >R!=>='>R=J >VH=4b=2l==[=^(Kp4=]={o===c+e=M0~sh=7n/>`b }>hu;&@>fd h:=bK>cQ =`^>?%ZI\>BS~VHo>fAo-~UVnx>(.ڌx>uD;k_>N6ͤWN>nMn> 7M\>F^v54>r¾^P>4*?>1þ,ǽ>G}oY=ol>=+7ѽ<=,'̽= pX!<7ҽ&¡p'ٽ="ν⽴KCK=j2ׇ&z,=`9 ~=~=#>SvvO>deel.>7\v qV(\>}d9k>0SV~>dB sx>GT3vdH>IC_>/$ك>Vy>0$}n>ě+^'a%|>hq C+zh>oN~js>8}7>9 'O-ҥH>֚þ/">횾q|'>}P>ľU0}=n+0=oQw|=+Ky$ >ZBo#{4|2'=Uʽ9!ܻҀ|e<=ƽh6BL-IJ:X6=Y\\= Սep~:A=z=7K?8>:tױjgg>^=c:쾲=>r$M`An>QiatԱ>p6Sp@>`BҋsƆ>Q羻>AAb>%/豾>vՖ>5.\Eh>Ȱ >(g}>a쾔'~˃>s,o>A$C,>$)X>v׾9Y3>|:dܾi`>?*q=%K>Hj2Qξ>m=cӾ- >YdBnN=}0;ļmþc/<]ذȾ -g=7#B d0i\="/?>pQo>3`=-Kd<>5Ln,#j>H^>1PH> A?)/>%N 1>L,E \>[*%;g>*mp'66>M*$ E>1>  >L߾"ܔ>[hC:>g>]ՓC>|bn>ϔhSE=T)nX>['= >PJiG=j龼<9_: =0c<<<žv)>>e־_[=o ZJ2=D-m_>S]YD1>* m*An>;B >L.Q>x>|(3T>Q۾P8ړ>O*$J>-P/lr>𙱾 >}\%ӟ&>WHK-=>ff>> c>e>nHO>&:>xv>Zq>JѾ D>>h1>0v>Ծ>s;>B[4>o|>>>#3b>y5>l.$>}>4i>>VG#*?Ρ>X[U?O|>I0>~>_>_>mQ ?2Ϧ>49oA?D>'d?>r>{9>8Duk>>p>A{>OW>xJ1> B>\~ym>Yl>K־#>@Z>\5tp>R0>]׾[>q >Sh>]yJ>b5u>&P>)HK>d>-n6R>7z>Qʼ>>I>Ke>K>]aL >t>L+MM>>8d>Cf>޽Q>kV>uth>F,>> >wwtD>X4>lu$&>=~f>D=n3v">=[f>D1=׾>S=Yl>W<ڟվU>7<#H>D=һ>{=;6J>{mR O<5>Υ=gͨ=2OeP>H =a셿b>;=}x9>!=톿~]><_y/>Zo.>b<69p>?{nM<>Gvn|>* QӾؗ>>jNq>=Ҿ >lX*>:~jHq>{miF5n!>¿?48>k^$ >hIQfl_>c[M>>r+g >.Ms>0{>4Y>]b}yY>cɉ>Wf~C>VuV>_->ff]:J>T"uѾci>S>y6y>0r;ѾA>7}PY5)>%$Rߘ>_5f>쏾<>cLN>jG$kh}>@]OK>ʯi?>=ى >BQ4~"X>=3ɉ>_~p>0b\><\Ԧ>CԾ>8D%p>>;!>@R6>jPN>1!j>s~~tH>S{*F>Ľ(,1>"ؽנ->㽶8!I>m9#>0A>y樽8>栿|k> ۸> Fn>=N3>= d>y?m.>("%?N'?"㠿D ?w\.dq#?4 3 ?f0]?A:AQ?A*?\R咜?PX̀i?`# ?F~9ϡ*$?&ۡN?)?ڇX$?\𝿣'?‡a-?(3dL0?i4h+??ß贝.?ƅ41?5B\1?7e.?|ᬾx.??lJB&?go3c,?DҞB$?޾R*?¡^?[ϝ'?`3>gN# ?'=84ё?˹K'?uO?d?fZ?`az5G ?w*z w*?q&q(?%?s&z԰"?;)?*񘿛 ?Ҿs<(&?>u?aƾ톕? x7?( 픿S?, /"?ЇBin>e#c_>"7r뒿>;~> 6.S?ie/.gQ>>$o !>/ E>xQ&L?Z〾>2?DanN>-ul:W?;B{?oOS6>f˰˭>KcgJ>_*̑S ?E^t?\Cr ?𔜾<<"?Gw:?!Ol?4 NS >$ƾ>4Ǿ̲ 6>䪾@q>wɾ>?S.1?tоb?Pھp쏿y ?0侐$?A?Zl勿K>^> UT>O>]̾;H>Jھwz>y >]Zg>$2mI>9پz>]܌? |?e?G ?E>+#>۪>jƗum>!a>5>>m܎/:>=s>_=^Ϗ~*>z>y~>v>>Sg$>o+>Т>D)>>|=#>z">>ЧA>0L>) r|.>eԽc>hϼ~ >^X>62Ş>>}nJ>>]>>?ڳ>g?n>7>ꎽ>$ON,?D> >_n>H]? > ?^5>?*?G>^I?>TC?1>,j?H>#I?gW>C$qA?>蜿~?> Tm?I>{Q?%G>ڠz ?>.͒9?>LߗX ?a>?;a>!RM?>? ?AC>YꈿF?M>ꆿK? ?6|%? ?ID'?P>ԃ-!? ?L(?"?'.?`Q?)?"?DP,?<0?2;[1?0?c/?%?5c*?5?R0G/?S?0ӟ*? ?jya/?.? #q3?+[/?s&2??:5*?_?:?.?I ?#e)??Q8P/?C,?>2?h-?XŐ3? ?#h(?|?-?D|?M ,?,?Q=51?-?.?5E?y)?F+?n,?:>?Q3??? ss1?LL?ӈ5?kIM?S3?հ A>?=>le>O?u[#??tފ"?A?᜿V#? ?y2#?t? !O#?>]"?t5\?Me$>?>?u>V?Jm>ڬ?->tj?<Ȅ͟?,ӑ?yTZ>b`?@Q} ?]?ku?=??$ꤿ-zC?с?X B?B'u?;J)???rD??]2 C?t? ??Bu? ,;?Qu?pT=??" r@?,?ϐyvB? y?K_1?}w?UAU7??J[6?b*?kꐿ ;?Jash?g?ťf!i?'?%⡿^?c?;?J]??.h?+3?Krsg?%? Ě? \??ePY??e?bm?mb?̥?4`V?C ? VQ?ܔ? 6_? ?^Z??i+tL?>?:F??KO?,@?8`(vU?DE?w=s?*Y?[֥9s?N?QpM~? ?쥿~?׫?T/Is?PG?"q??l8iq~?l2?X1|??al?a?Z+o??95w??LKiz?r?z‘$?c??h??gk?b?6q??&RX?֯?r^?eZ?/]`?$?If?m)?ߓ?D ?u䥿1?U?ylVև?Y?Lץ:?[?2sȃ?? ?)?6'?~m?8?q?x??Ԩ??!??tׅ? )?s?? 3z?>U?82z???>?_g?0?m??Ԡn?8?Yt??b??p}Z?ō?pݎQ[??㌿JT?_:?ah?I?#ib?9?*IR??䊿,J?52?G͘L?^?']D??Νz`@?J?`ە6??0و7:?4?Ň0?6Ǎ?L9w?l?I)?!?M'2 ??a$"?"?ό\O??ʟU??m,eJ?WR?S̎P??KU??~e\?5?܉>?? '/G?7?'8?5?wߵA?$? 4?I?*?~G?E.?S?$??gB?t?3 D??*珿6J???>??H?i^wE?G?O)P?[?J??jۊ2?>?'E;?ػ?싿,?yy?k6??aW)?>? i?ձ?}`#?&ȧ??.I?1?~?9? ?+"=?~?AgA4?~/?w]op7?F? B??iq;?Pϼ?;Oi'? ?VȎMn0??1H"? ?!+??M? ??4Q??!?c??懿t?Ġ?:m?夗?SN>?E㉿?5?r/?y?^3?g?U,?Б?M"?Q?f%?6?lTJ??u ?š?`'??<^?a?  ??T?x?᏿4>^?Y>a١?C)+>?awE> ?)?b?:0??gݟ0(?]?~.??ᩛt7??X5???t8?g>??U`??ĞDQ ?q???>0?5#>?Cd&>H~? ɶ>9?̢_?J(?x> ,?B??Z즿qn>O?⢿[ ?? ?5?c`>p?as>S'?{sP>Z?&#t>?<§>1?ǟƿ>u?ZL'>_?d1"??"!?Y?ӱ!?Q?K?ea?>=??P+)%?)?ag"??՞i??֦/?~?8=74??6*P4?D?J2.?,?+3})?5n?M:+?Z?4?y2?]+?^?奔x0?P?'?zK? w&?z?Qs#??SP???|?©&?? ?g?"?ä?1a>?>?#_>G?QH>k?GS>qҔ?ቿ->'?_S0>?T>"$?鄿H?zk?؂?8?S?u?#!?y?]5'?Z#?r?z>??>q?*/?c>?a?u>|?v?3h>"?Д?G>H??d>_?pk?>?38?>?1W?{A>??>?ޡ?>Y?g?yU>l2?R?I>.?g?M>@y?ި?[>, ??>@5??ʉ>+?p?s>??b>r?0?~p>,?W?a>\?O?:>rO??'>Ub??>Q)?HP?6>)?U?>v?#?o>~?b?JD>?"?O>{S?h?o5>B?.?>??7>c?T?>}?^?@>d?fD?t>r9?u:?o>k?OB?@n>+?$6?'i>o?f0?]o>$?;'+?f>t?P?}> ?)\?Є>a@[?>?6O?)Ly> ?{7i?‹>=;? u?Ɠ>K@xu?mK>K4@ʠh?c܉>,?U?֜>M?5ه?k>Z?[Z?"Ԥ>?l?el>q?&?y> Q??p>J?ȶ?ٿ>?o?`>?K?Y$>T??_>b?͎?>Y?|5?R>&??]C>9 ? *?B>?>6?ҮJ> I?B? KR>I@6O?_>@_]?Zj>@dj?{>)@w?>ɬ@r?7>;@̯?>?y?">?z?>>!??$>Ĭ?Bg7?l>P?_D?P|>'$?(;?>X ?H?]g>,?aS?>?Aa?6>?U?P>k?Jc?i>y?#p?>?vo?8>?\}?s>ʰ ?>Y}?V>ވ#?'z?ڠ> ?1߅?>#?rӌ?>w&?K?J>*???s>Rt'??>!w+??>-?;7?>53??t>0??L>`7?b?G>9?O?>?Ґ?a>E?M??F?g??JN?t!?w?M?'?->ˁU?J?>U?)?x>ta?I?Y>7b?鋦?> m?F?Y>l?M?|>v? ?ee>Sw??8>Ô??>??>#? ?O>τ?(?f>?i?nY>H?H?>Ӌ?1?>?F?s>nߐ?6z?J>d?΃?>*?}?>I??>$H?}?ŝ>6{?Mv?d>V}%?}O?>l>e$?g)Z?>#?Hd?Z>o4?h?o>х? r?_7>N$?gq?D> &?,~?>?5?>4b|?鐖?>L(? ?ߢ>e+?? >s?l?>j?B?Q9>2??5>.??!>O?qܥ?H??H??G?V?9?/>:??ɍ>@A??>\e6?4?>`??>P)?B?P>?>i?ͻ?.&?,d?FXɨ?YV??lg?l?$v?IΉ?i#?̞3??Պ?R??7?S??O+? c?=t[?T?,ŞLj??%։?t?OA?"?: ?~??5?2vi?[?ڥ?I3?vN ?o?mJFK?_"?[jp ?P?K???E?Oz?1 ??/?*l??~֧?n;?;܃?M?Gp??4m?{?Ԣ?O?kF??5}7?mx?n?)?}?h#?ߢ{a?F?اG?P?s'?i?񬟿n}??򊦿B|?A?F˩by?i?NK`y?o*?ĩS`|?P?Et{? f?=ɟGz?X?)Ԯx?j?+v?$?שAu?S?$u??6ȩq??/rq? ?t? ?; s?O?Jp?:?t o??Mk?:?e䩿zKk??fe?bC?;e?y?y̢k??dZPj?b?LZe?P?e?2?yx a?I?Ω)`?5l?IG\? ?u[?G?#`?"?DD[a??ࢿ\?r??+oj\?z?`W?/*?שLW??榿IS?Y?穿mS??!{W?`?W??y3+R?P?򺟿9'S?+?M?ڼ?zM??N6G??|p@G?KK?߮N??TEM??ڰH?C?o_ H?Z?+C??|T3C?C?cѧ?? M?:V8???wlDB?O?fz:B?>?=?o6?'B=?~?h9?? S9?Mg?Vb$8?D?a#9??K!c?$?ʛ^??&˜a??3ۘN\?0]?Z??!5.X?YQ?+ul?G?`̛(h?K`?PKi??嘿`e?,?\q]p?$]?4t? ?;љp??2l?w?`z?&h?}\w?6?'v?K?™js??嘿rA?6?DʖqI?T?ԙ;H?Y?Oa? ?8:׃?@U?a$2??@іa?Q?x2w??. ?R?ьv??3T?? g}?A?g*{?S?>jl?_?Kq?@?xcz?c??J i??xf?M?R[??`??fYQ??7`V?}?{M?R?{MT?0?eP? ? NL?? Q??|M?e?AbH??TUU??ƛ;??PA??c>u?1?@Q~?4?[?}?ef?.?/걄?x?T턅?@?z?b ?yÛ?|?Fz>x?.?>UJ? ª>J5?ge'>jŠ?O,q>ۈ?RM>Dž?CS>?ԣ !e>?/w>%~?蝨?>?E>FK?Pd+]>‹?x=d>l?>؋?_= 8>l?*=m?᡿ >~1?9q;=܁?|=i ?৿ڿr0?F=.?@?!hg=]??mU={c?8?"Pg=[^??%օ=Va?!?߷=Ig??Di=e??#=c?ä?=-d?C?=g?ㄤ?=g??=.d??QhP'?U?T$?N?<*>O:s?gȪ?>p?N_?C>m??j=umj?Kn?=d??E/=$:g?~?hX˼-?ȏ?̼/?'D?xO>?\?7i1;6?E? e<'6P??Z;G?P?}O&=k\??\?B13B??I\t9?U?\O=?`??tD]= a? ?_ Q=3`?!?UI=~_? A0 8       !!"#$"%& ''()*(#! ++,-.,)'//0120-+3 345641/$7#789:8;<$%*=)=>?@>97*#.A-ABCDB?=.)2E1EFGHFCA2-6I5IJKLJGE61MNOPQRQSTSNUVWXYZ[ZPRPWO\]^_`a`Y[Y]Xbcdefgf_a_c^hijklmlegeidQnRnopqorsQTZt[tuvwupnZR`xaxyz{yvt`[f|g|}~}zxfalm~|lgqpqrwvwp{z{v~z~            !"!#$%#&%'()$$)'*(+,((-+.,/0,,1/213450636578 94:7:9;<=8 8=;><?@<<A?ABC @@DCED"FGHIJIGKLFFMKNMOPQLLQORPSTPPUSVUWXTTYWZY[\]X^[^]_`\\a_bacd``ecfdghddigjikHhhlkmlJnopqrsrotunvwuvuxtyxz{w|}{|{~z~           ! "#$%#$#&"'()(&*+(*(,)-./.,0120.3/3245167565849:;:8<=:<:>;?@A@>BC@B@DAEFGFDHIFHFJGKLMLJNOLNLPMQRSRPTUVTRWSWVXYUZ[\ZY]X]\^_[`a_`_b^cdedbfghfdieijkjhlmnljokopqpnrstrpuquvwvtxyzxv{w{z|}y~~}|           ! "#$%$"&'(&$)%)*+*(,-*,*.+/010.23420515467389:87;6;:<=9>?=>=@<ABCB@DEFDBGCGFHIEJKIJILHMLNOKPQOPORNSTUTRVWXVTYUYXZ[W\]^\[_Z_^`a]bcabad`efgfdhifhfjgklmljnolnlpmqrsrptuvtrwswxyxvz{|zx}y}|~{~        qsqp-,-23238989> !"#! ?>?#D$"%&'%$EDE'J(&)*+)(KJK+P,*-./0/1-,QPQ1V230.WVW3\425]6b7898654]\:;97cbc;h<:=>?=<ihi?n@>ABCA@onoCtDBEFGEDutuHzHGIJKLKMNMJOJMKJPOPQRQJLJQPQSRSTUTQLQTSTVUVWXWTLTWVWYXYZ[ZWLWZYZ\[\]^]ZLZ]\]_^_`a`]L]`_`babcdc`L`cbcedefgfcLcfefhghijifLfihikjklmliLilklnmnKNKlLlKno\^\p[qrsrpturtrvswxyxvz{xzx|y}~~|~~{z_a_^otuztbdbaFIFEIIH{z{          YXsqY[ ! yws "!#$%$"} y$&%'()(&$(*)+,-,*(,.-/. ..V0U12320X24356764!46879:;:#%#8!:<;<=>?')'=%?@>@ABC+-+A)CDBDE//E-SFRFGHI131GUIJHJKLM575K3MNLOPQP9;9N7PRQSTUT<><R;TVUVWXY@B@W>DDZBY[X[Z\]^]__`a^\[[aXb`cdecbVXVeUfdghigfSUSiQjiklmkjOQOmLnmopqonJLJqHrqstrsrFHFuRPuO]]v^wxyxvz{yw`^`{c|z}~}|dcdg~hghklklopopststOMNxyyz}~}~Nnmmkjjhgged        !" ! #$$#%&'%$''()*)+ ,,+-.,-,/00.123103425675478869:898;<!<;=><=<?!"@%@?AB@A@C%&CDEFEG*(H-HGIJHIHK-.L1LKMNOMLO12P5POQRPQPS56T9TSUVTUTW9:X=XWYZXYX[=>\A\[]^\]\_AB_`abacFDdIdcefdedgIJhMhgijhihkMNlQlkmnlmloQRpUpoqrpqpsUVtYtsuvtutwYZx]xwyz{yx^]^{z|}~}b`eefiijmmnqqruuvyzyz~|                  !"#$""%&'&($$(#)*+*, &&,'*-+- * *-./0/. ./-1+1.0.- -.1/202//234541013+31463537+)8'879:898;'%<#<;=>?=<!#!?>@ABA404C5DCEFGEDG56H9HFIJKIH:9:K=LJMNOML>=>ONPQRQSB@TETSUVTUTWEFWIXVYZ[YXJIJ[M\Z]^_]\NMN_^`abacRPdUdcefdedgUVhYhgijhihZYZk]lkmnlml^]^oopqrqstspupsqvqtqwrwvxvwqyz{|}~}zy}~}tvtvxx~|{~usustswrwxurpur                 !"#!#$"$%&'%'(&()*+)+,*-./.,+.0/12320.24356764268789:9;;9<:<=>=??=@>@AABCDCEFG"E GHFHIJK$&$I"KLJLMNO(*(M&OPNQRSRP/-O*RTSUVWVT31R/VXWYZ[ZX75V3Z\[\]^_8:8]7_`^`abc<><a:cdbde@@e>fghgijkCFCiDkljlmnoHJHmFopnpqrsLNLqJstrtuvwuSQuNwxvxyz{yWUwS{|z|}~}[YW}~\^\[`b`^ddbgjghlnljprpntvtrxzxv|~|z~|}vw|npnv                                           p           ! " # ! # $ " $ % & '  % ' ( & ( ) * +    )  + , * , - . /   -  / 0 . 0 1 2 3  1 3 4 2 4 5 6 7    5  7 8 6 8 9 : ;    9  ; < : < =    =  > ? @ ? A B A > 6 > A ? > 4 6 4 C 2 D E F E C @ G H < < H : I G J K L J I 8 : 8 L 6 A L B M N F D 0 2 0 N . O M P Q R P O , . , R * S Q T U V T S ( * ( V & W U X Y Z X W $ & $ Z " [ Y \ ] ^ \ [ " ^  _ ] ` a b ` _    b  c b d e f G G f J g e h i j h g K J K j B k i l m n l k ? B ? n @ o m p q r p o E @ E r F s q t u v t s M F M v P w u x y z x w Q P Q z T { y | } ~ | { U T U ~ X  }  Y X Y \ ] \ ] ` ` a d  e h h i l m l m p q p q t u t u x y x y | } | }     n  mj m if i e` e b                 y   y{ {                                                     ! "   "  # $ % $ & ' (  !  &  ) * + * $ ' $ ) % ) $ * , - . - * ' * , + , * - / 0 1 2 3 4 3 0 ' - 0 . 5 6 7 8 6 9 : 2 4 2 5 1 ; < = < 8 9 8 ; 7 ; 8 < > ? @ ? < 9 < > = > < ? A B C D B E F ? 9 ? B @ G H I H D E D G C G D H J K L K M N O P Q P M R J S R S T U T J L J T S V W X W Y Z [ \ ] \ Y ^ _ \ ^ \ ` ] ` T L T a U b c Q O d N d c E H b I ( e ' e f g h i j i f ! k ! i l j l k m n   n  n o m o p q r p  s t  r u q u v w v s x y v x v z w { z | }   s  s } x ~ |  ~  ~ y x y      |               % #    + ) %   . , +   1 / .   5 7 5 1    = ; 7   @ > =   A C A @  $ G I G C $% * b Q b I *+ 0 P R P Q 101 6 S U S R 767 < ^ _ U <= B X V ^ BC HF E d N F 9 : 9 4 3 4 g e 3 ' N K L ` L ` ] ] [ Z                g                       h j h  g                            l m l j  !  ! " # ! " ! $   %  % # & ' ( & %    )  ) ( * + ) * ) ,  , + - + , ) . o q o / m  0 " 0 / 1 2 3 1 0 # " # 3 & 4 2 5 6 7 5 4 ' & ' 8 * 8 7 9 : 8 9 8 ; * + < - < ; = > ? = < @ - @ ? A B > C D E C B F A F E D G H w u . q . I 1 I G J K L J I L 1 2 M 5 M L N O M N M P 5 6 Q 9 Q P R S Q R Q T 9 : U = U T V W U V U X = > X C Y W Z [ \ Z Y D C D \ [ ] { G w G ] J ^ _ ^ K J K _ N ` a ` O N O a R b c b S R S d V d c e d W V W e Z f g f [ Z [ g h i j k l m l i n o h p q r s t u v u r p o q n w x y z { | { x v t w s } ~ | z  y  ~ }  j h p  ,  p u v , - @ - { v { | @ A F A } | } F  j Z W X W Z m k j X U Z UTO T NI N H X  y            w s w y q n q s                                                          l  m      l n            ! " # "  " " $ # %    $  &  ' ( ) ( &   ( * ) + , - , *   ( . / 0 1 0 .   . m 0 2 1 3 4 5 4 2   0  4 6 5 7 8 9 8 6 # ! 4  8 : 9 ; < = < :  % 8 # < > = ? @ A @ >   <  @ B A C D E D B ) ' @  D F E G H I H F - + D ) J J K L M / 1 / K M N L O P Q P N 5 3 M 1 P R Q S T U T R 9 7 P 5 T V U W X Y X V = ; T 9 X Z Y [ Z \ ] ? A ? X = ] ^ \ ^ _ ` a C E C _ A a b ` c d e d b I G a E f g h g i i f j f i g k l j i l m k n o p n m p d q o r s t r q c d c t  u s v w x y x z h z w { w z x | } { z f h f } j ~ |   ~ k j k n o n o r s r s v y w { | { |                                         b                     !  ! " # ! " ! $   %  % $ & ' % & % (   g g ( h ) aRa* b +  + * , - + , + .   /  / . 0 1 / 0 / 2   3 " 3 2 4 5 3 4 3 # " # 5 & 5 # 3 6 QBQ7 R) 8 , 8 7 9 : 8 9 8 ; , - < 0 < ; = < > 0 1 ? 4 ? > @ A < = B C D E ? @ ? C 4 5 B & F B D B ' & ' F h F ' B G A0AH B6 I 9 I H J K I J I L 9 : M = M L N O M N M P = A Q @ Q P R S Q R Q T @ E U D U T V U W D F x h x W y X U V Y Z 2G 0G [ J [ Z \ ] [ \ [ ^ J K _ N _ ^ ` a _ ` _ b N O c R c b d e c d c f R S g V g f h i g h g j V X y j k l Y m \ m l n o m n m p \ ] q ` q p r s q r q t ` a u d u t v w u v u x d e y h y x z { y z y | h i | } ~ k  n  ~   n o r r s v v w z { z {  }   ~   b} ~ aFa b a *E F )) * )                                    !"# ! $ %&'%$ '(&)*+)(+,*-./-,/0.12310342567897:;<=<7>?@><@=ABCB?DEFDBGCGFHIEJKLMN686L>OKPQRPO?>?RDSQTUVTSEDEVJWUXYZ[\KMKZP]Y^_`a`b^]QPQbTcdedfghijifklmamnopikinjqmomrarstusvwxyxzec{k{z|}{|{~kpo~oqvyw||}::         !"!#$%&'&#()&(&*'+,-,*./0102()3.32453436.78986:;<=><?;@AB=;C@CBA%D$DEFFE'+F'FGG+-+GFHG-GI>J=JIKLMKJA=AMNLOPNONQQPRPQNSTRQTTUSV7,.,W-HXKXW9YX9XLKLZO[Y\]^\[POP^R_]`ab`_SRSbVcad0e1fghge" 0(i8:8Y9Yj\kilmnlk]\]n`ompqrpoa`ardsqtu uvhfw1wvxww/1/x4x/wyz{|zxy4y}45~:~}y{~i:ilmlmpqtqp            !  " "#$%#&&'()"$"' *+,-,*(&./010.-+2345421/66789:;:7!%<$<=>?686=@<><A$)B(BAC,D-EFGFDCB,(0H1IJJHGE0-4K5LKI41@MCMNOPQRQN>FSGSTUVMOMFCPWOWXYZ[\[XRV]U]^_`WYW^OZaYabcbdedf\`g_ghijacahYkklmlnnopqrqomk s!tuvusrp :w;xyzywvt:!{|}|xzx{;{x|l~m~lqrqmuvuryzyv|}|zL535{|{        }{;}98;?> 8!" "#$#!%!#""& '()(*+*"$, ,')' ',(-)./0/-12(+,3 345460.6)789:9753 ;<;=>;:8??@AB<=<@CCDEF?A?D/G0HIJIGKL/14M5NMOPMJH409Q:RSTSQON95>U=UVWXVTR>:BYAYZ[\UWUZ=F]E]^_`Y[Y^A!a%ba ! ScTdefecghSO`i_ijklmnmj[eofpqrqostegpufuvnwvxyprwlnlzkzwxwzl{|}|tgt{s{t|X~Wunu~fdXT\W\m[mnm\|}IKIJgPJPhOhghP;:=CA=GHGCe                         ! "#$%&'&$! ( )*+*('% ,-./.,+) 00123-/-14567642089:;:875<=9;9< >!?>@ "&A'BA?&!*C+DCB*'.E/FED.+3G2HGFF3/6I7JIHH62:K;LKJ:7=MMLL=;NOPPOQRNySSTUVPQPTWWXYZXUS[[\]^WYW\_ `_ab_][ cde8eca`  lfjfghigjkla`kakljmnonl^npoqrsrp[Yn^fhjhtguvwvthrxsxyz{\M\y[d|e|}~uwu}g|xexRyRQ~{zeaeN8N{MihmomijimhvwqsqoVUw~VQwZUZYZwYss^Y^]^xs]baz9d8d9:9dNNyNe IHIJIHvutuvWJXJ            !"#"$%$&&'()"%"(#**+,+-./001231,* 445670205$8%89:;464$+<,=>?><@A+.3B2BCDEC?=3,7F6FGHIBDBG2;J:JKLMFHFK6>N?OPQPNRS>@ETDTUVUWQOE?IXHXYZ[TVTYDM\L\]^_XZX]HP`Q`abaPRPa`UcVdefecb`cQ[gZhgijdfdgV_k^khih_Z_hklmnonlpqrstutromvwxyxvus'z#{|}|zyw'!~~}{!#   c e c ` nonptuoxyxu|}y}^ ` ^ \                ! "#$%$"&'()*+*(%#,-./.,+)!010/-!$2%3454267$&*8+9:;:853*%.</=>?><;9.+1@AA@?=1/4B5CDEDFGFB6:H;IJKJHEC:5LMKI>;>M?NLOAPQQPONA?DRERSTUVWVSGJXKYZ[ZXTRJEL\O]^_^\[YLKQ`aa`_]QOUbTcdedbfgUWZh[ijkjhecZT^l_mnonlki^[apqqpoma_drestutrvwdfjxkyz{zxusjen|o}~~|{ynkq}qotuvz{u~{L J  Q O L U S Q Y W U [ \ [ Y Q>QR[R[\                !d\d"e# " $ $%&%  %$''()*$&$( +')',--,.-//-.-/0012110!3 4565320!#7e789:4648 1;2<=>=;./15?6@ABA?><52:C9CDEDFB@F6=G>HIJIG)+=.AKBLMNMKJHA>DOEPQRQONLDBISJTUVUS&*I)MWNXYZYWVTMJQ[R\]^][ZXQNU_V_`ab%%`&cded_a_cVc_dYfZghihfecYV]j^klmljig]Zbnanbbndoepondahqirqphelsmtsrliuvwxvyz]_]uU{|}~xyx{w~}zygigz_y                  ! ""#$%&'&#(()*+"$")!, ,-./(*(-+0*01234541$/6.67890207*:;;:':;3<2<=>>=59?8?i@<@2AABAC&C';CA:D'%4$4D5E:E>5>EE>bFcFGHI797GeJKLKFHFJcJFKMNONJLJMcMJNjPi?P8QMOMjcIRHRSTUCECS9VWXWRTRVHVRWKYLZ[\[YXVKHQ686].^_`_]OUaTabcbdRPdEefgfacaeTeafhigeWTWiXjhk[l\mnonlkj[Xpqrqstumoms\^,.,v wxyxv`bzc{|}|z^\bRh~k~hgnokutotyyw y##"#tmk|^|}mrpZ\ZLNO_O_`r_qtqr`x`xyx{}{c{fcfg}}           !""#$% #&&'()"$"'**+,-&(&+../01*,*/22345.0.3667892427::;<=686;>"@??:<:@Ad I d @e BCH - H BI ADE,  , D- CF G  F E H I H G  JK J I LM L KNO N MPQ P O!R STUTQ Q!%V$WXYXSUS% )Z([Z\]WYW)$-^,_^`a[\[^(1b0bcde_`_1,5f4fghibdbg09j8jklmfhfk4=n<nopojljq8?rssrpn?<TtUuvwvt T XxYyz{zxwuXU]|\}~~|{y]Ya`}}\ed`ihdmlhoplsqqppvw  v z{w~~{p     DB    hfD        !"!## "  # $ %&'&$ h ()*+*('%  ,-./.,+)012320/-45676431898:;575!<"=>?>9:9<#@@ABAC?=C"&D'EFGFD&*H+IJKJHGE*'.L/MNONLKI.+2P3QPRSMOM2/6T7UTVWQRQT3;X:XYZ[UVUY7>\?]^_^\ZX>:A`Babcb`_]A?FdGefgfdFJhKijkjhgeJGNlOmnonlkiNKSpRqprsmomSOWtVtuvwqrquR[xZxyz{tvtyV^|_}|~xzx^Zbc}~}b_b}fgfjkgnonksrowvr{zv~zc5~v u     v                54 B@acaB          2125c.-.1  *)*- &%&) " % ! "#$!%&'&$!"(%)*+*('%"!&,)-./.,+)&%*0-12320/-*)/405676431/-2859:;:875208<9=>?><;985:@=@ABCA?=:9>DADEFG@B@E=BHEIIDFDHA.J/JKLK.+.KJ2M3NOPOJLJM/>Q?QRSR>;>RQCTBTQSQC?CQTGUFUVWXTSTVBIYZUWUYF[:7:R;R[S[R:\]^_&!&]'`\L`*'*K+K`L`K*abcd " b!_a^[eSefPN636f7\gLhgijklk\^OmPnopomihOLeqSqnpnePenqjrirstuvwvjloxpyz{zxtroiqXSX|W|}{y}pk~l~aca^vw"~~l# #"wutuwu z{ztz{|ZWZ|{|Z'&',cdcd"d"#"   !&!   VGV W   F6FG   &76'& !" ##$%&!'()(#%#''#(**+,+-./010.,*"2!234351/5&6%67892427!(:):;<=686;%+>,?@A@>+0B1CDEDBA?0,3F4GHIHFEC319J8KLMLJIG94=N<ONPQNMK=8@RASTUTR@DVEWXYXVUSDAHZIZ[\]WYW[EL^M^_`aZ\ZLIQbPbcde^`^cMTfUghihfTXjYkjlmjigXU]n\nopqolkoYar`rstunpns\evdvwxyrtrw`vzd{|}|~~zxbP}{bdO<OP:)<|}|}~xy~srsx         )   '   ')  !"#! #$$#%&'(('xz&k()*!*)ry(x*"!"+%,qsq+r-&&.ki._/0-]_]0E 1/CEC123456763%#2$28$*9+98:;249<+2=1=<>?9:=@1L@KA@BC=>Db6%6D7EFGHI{}{Gs,F%JABAKK}IJHEL7MLE5N4ONMMN7;P:QOOP4?R>SQQR:CTBUSST>JVHWVUUVBFX%$XWWXHYZ[\[YZ]^_`_]\ab`^bcadefdcfgehijhgjkilmnlkoonpqoporssrtuvtswwvxyzxw{{z|}{|{~~}}{wuwuSUSGJtJG_\_`uhu tsda`adg"ghdcVedeh!$!"VU}|}ihilD$C@utuxCpqt@?lmpyxy|[\   [hihmli        uputu !"! t !#$%$&" &ty#x'()(%#(x~'*''**'+**++*,- ++-,./  , ,/0121. .00.133020034452366547  7 142888896698:)%);;;<$"$=%:=<>?!!>"@pA?A@qlq@p@qB CDEDBDDFEGHIHFHHJIKmlmJrCL  LWMNONLEGNENPOQRSRPIKRIRTSUgfgTlMgWgVfVMOMVgVWfwXvXWYZVOQZOZ[Y\]^][SU]S]_^_``a`fXvabc cXYb b b \dYc d ed^_e^ef [Z[f`ghijkkglmnlknompqrpossrtustsvwwvx yyzxwz {|}|~~)||})%ra_UTUXrtXtjiy//.yxy/8%}8:8A@tux.SRpqt@abmlmpRgiglbgk^ki\L\^J:{}{JLefiebfjii     "!$#"&%""&(' *)(!#(( !,+.-,'),,'0/210+-00+43654/144/87$:98<;88$<>=*@?>79>>*7BA.DCB=?BB.=FE2HGFACFF2AJI6LKJEGJJ6EPONNQPNSQMUNYXWWZYWPZVOW_^]]`_]Y`\X]edccfec_fb^ckjiilkielhdionQqposrooQsutZwvunpuuZnyx`{zytvyy`t}|f~}xz}}fxl|~l|qqww{{            ! #%$%&#%$'%))$*'))(*-,-(.+--,.101,2/1031540556047598499:48;9==8>;==<>B@B<BA<@CBD D@ECD "DGFHGGIHFKGMLMFNKMLOMQQLROQQPRUTUPVSUTWUYXYTZWYX[Y]\X]]^X\_]a`a\b_a`caede`fceedfihidjgihkilHlhmklHJlqpoorqutoonuxuwytx{zxxw{~{}z~~}~         ! #"  #&#%'"&&('%*&,(+-),,.-+0,.0223.5422158579488:97<8>:=?;>>@?=B>D@CEADDFECHDJFIKGJJLKINJPLOQMPPRQOTPRTVVWRYXVVUYYZ\\]Y_^\\[_b_ac^bbdcafbdfhhidhjiglhjlnnojnpomrnprttuptvusxtvxzz{v}|zzy}}~}            "!#""$#!&"$&(()$(*)',(.*-/+..0/-2.02445076443778::;7=<::9=@=?A<@@BA?D@BDFFGBIHFFEILIKMHLONLLKOROQSNRRTSQVRTVXXYT[ZXXW[[\^^_[a`^^]adace`ddfechdjfikgjjlkinjploqmpprqotprtvvwrvxwuzvxz||}x~||{          qp-2389 >  !##? $D##"$$%''E$(J''&(()++K(,P++*,1/.,-11Q,.V1.033W.4\33247b66874566]479;;c7<h;;:<<=??i<@n??>@@ACCo@DtCCBDDEGGuDGHuFIGp\oq[pprqotpvruwsvvxwuzv|x{}y||~}{|~{{z_o^ouutbaFFHI{H{                 XqsYq wyw#!""$#}"" }'%&&('&&$+)**,+**(/-. ..,.1U002102VX0534465!4249788:98#:6!8=<:?>=='?:%=A@?CBAA+C?)AEDCEE/C-EGFSIHGG1ISUGKJIMLKK5MI3KOLNNPON9PM7NSQRRTSR<TP;RWVTYXWW@YT>WZDYBZZ[YZ_]\__\_\^aa[\bXaa`bbceeVbfUeedffgiiSfjQihkijkmmOjnLmlomnoqqJnrHqpsqurtuFrPRutOuv]w^vvxwvwy{{`w|c{{z||}d|g~hklopstMONxxzyz~nNnkmkhjhegd          !# "##$"%#$%''$'&'+)( ++, (-+/,.//0/.00133043324457778768;8:;;<:=;?<>"!??@">A?C@B&%CC&BCGED(*GGH(DIGKHJ.-KKL.JMKLMO21OOP2NQOSPR65SST6RUSWTV:9WWX:VYW[XZ>=[[\>Z][_\^BA__B^_ca`DFccdD`ecgdfJIgghJfigkhjNMkklNjmkolnRQoopRnqosprVUsstVruswtvZYwwxZvywxy{{^x{{z}|`b`|fefjijnmnrqrvuvz|~|             $#""$"!"(&%(($%#(,*) ,,&)',736)+778)697;8:%';;<%:=;<=??!<??>CA@C4AD5C@ECDEG65GGH6GFHHIKK:HL=KKJLLMOO>LOONSQP@BSST@PUSWTVFEWXIWWVXXY[[JX\M[[Z\\]__N\__^ca`PRccdP`ecgdfVUgghVfigkhjkZhl]kjmkolno^lono|{zz}|zzy~}vxx|~|rwuprp               !#"!!#!%$#'&%%'#%)('+*))+')-*,,.-,,+1/002100.534465442;86;98;6;?<9?=<?9?A@=AA=AECBGFEEGB EIHGKJII$KG"IMLKONMM(OK&MQNPPRQ-/PPO-USTTVU13TTR1YWXXZY57XXV5]\Z_^]]8_Z7]a`_cbaa<c_:aedcee@c>eigfkjiiCkfDimlkonmmHokFmqposrqqLsoJqutswvuQSusNuyxw{zyUWyywU|{}|Y[{W}\}[`^dbghljpntrxv|z~}|}wvwnn                                                                !  # " ! ! #  ! % $ # ' & % % ' # % ) ( ' + * ) )  + '  ) - , + / . - -  / +  - 1 0 / 3 2 1 1 3 / 1 5 4 3 7 6 5 5  7 3  5 9 8 7 ; : 9 9  ; 7  9 = < ;  = =  ;  = C 4 > D 2 C C E D > @ C H H < I : H H G I I J L L 8 I A 6 L K B L D F N N 0 D O . N N M O O P R R , O S * R R Q S S T V V ( S W & V V U W W X Z Z $ W [ " Z Z Y [ [ \ ^ ^ [ _  ^ ^ ] _ _ ` b b  _ c  b a d b f f G g J f f e g g h j j K g k B j j i k k l n n ? k o @ n n m o o p r r E o s F r r q s s t v v M s w P v v u w w x z z Q w { T z z y { { | ~ ~ U {  X ~ ~ }   Y  \ ] ] a ` a  e  h i h l m m q t u x y | }     n  nm j ji f fe ` `b                    y {              "   "    "   " & $ # ( ' & &  ( #  & 2 1 0 0 3 2 - ' 0 / . 0 8 7 6 : 9 6 6 2 : 6 5 2 D C B F E B B ? F A @ B M K J O N M M P O J R M Y W V [ Z Y Y \ [ V ^ Y a \ _ a ` \ a T ` _ U a O Q c c d O H E c c b H f e ( h g f f i h ( ! f i ! k k l i n m k k n p n  p o n r q p   p r  t t u r t v u t s v z v y { w z z { y z   } } s  ~ x } } | ~ ~ y ~     |                # % #   ) + %  , . ,   / 1 /   5 1   ; = ;  > @ >   A @  $ G C %$ % b I +* 0 P Q 1 6 S R 7 7_ ^ U =< =V X V CB Cd E N F : : 4 4 e g 3 e K N L ` ] [ ] [                                h  g                              l   j !   " $ ! #   $ $ %  $ # % % & ( (  % ( )  ' * ( / o .  m / / 0  . 1 / 0 1 3 3 # 0 4 & 3 3 2 4 4 5 7 7 ' 4 7 8 ' 6 9 7 ; 8 : + * ; ; < + : = ; < = ? ? @ < B A ? ? > B B C E E F B E E D u w H H . u H I . H G I I J L 2 1 L L M 2 K N L P M O 6 5 P P Q 6 O R P T Q S : 9 T T U : S V T X U W > = X Y C X X W Y Y Z \ \ D Y \ \ [ { ] ] G { ^ J ] ] ^ ^ _ _ K ^ ` N _ _ ` ` a a O ` b R a a b b c c S b c d S c d e e W d f Z e e f f g g [ f g g k j i i l k o n i i h o t s r r u t o p r r q o z y x x { z t v x x w t z | ~ ~  z ~ ~ }   h j h   , u p v , - { | @ A }  F    Z X W k m k U U O ON I IH y            s w n q                       m       l          !  " !  $ " % # $ $  % $ '  & & ( ' & &  + ) * * , +   * * (  / . . 0 /   . m . 3 1 2 2 4 3   2 2 0  7 5 6 6 8 7 ! # 6 6 4 ! ; 9 : : < ; %  : : 8 % ? = > > @ ?   > > <  C A B B D C ' ) B B @ ' G E F F H G + - F F D + K J M L K K / M K O L N N P O 3 5 N N M 3 S Q R R T S 7 9 R R P 7 W U V V X W ; = V V T ; [ Y Z ] \ Z Z ? ] Z X ? _ ^ ] a ` _ _ C a ] A _ c ` b b d c G I b b a G i j l l i m l l k m m n p p m q d p p o q q r t t c q u  t t s u z { } } f z ~ j } } | ~ ~  k ~ n o r s v w y w |                                                         !   " $ ! #   $ $ %  # & $ ( % '   ( ( g  ' h ( * a)  b* * +  ) , * . + -   . . /  - 0 . 2 / 1   2 2 3  1 4 2 7 Q6 ) R7 7 8 ) 6 9 7 ; 8 : - , ; ; < - : = ; 1 0 > > ? 1 A @ > > < A E D C C ? E 5 4 C C B 5 H AG 6 BH H I 6 G J H L I K : 9 L L M : K N L P M O A = P P Q A O R P T Q S E @ T T U E S V T F D W W x F X y W W U X 2Z Z G 2Z [ G Y \ Z ^ [ ] K J ^ ^ _ K ] ` ^ b _ a O N b b c O a d b f c e S R f f g S e h f j g i X V j j X i j l k Y l l m Y k n l p m o ] \ p p q ] o r p t q s a ` t t u a s v t x u w e d x x y e w z x | y { i h | | i { | ~ } k ~ ~  k } ~  o n o s r s w v w {  }  }   }b ~ E* F                                 #"!!#$ !! $$%''$(''&(()++(,++*,,-//,0//.00133043324987;:77<;6>7<>@A=@@BA@?BBDFFGBIHFFEINMLL6NO>LLKOOPRR?OSDRRQSSTVVESWJVVUW\[ZZK\]PZZY]b`_]^bbQ]_Tbfdchgffihckfnmlponnipljnsmqsrmutsqvszxwcezz{cw|z~{}pk~~p}~qoqwyw}|}        #! %$##&% (#*&)+'**,+).*20/)(223)/426357.66875:6>=<<>?<<;?;=BBC;BBAED%EEF%'EIGH>IIJ>HKIJKMMAJNMMLNQRTTUTTSUW,7H-WWXH79WZXYZLX[OZZY[[\^^P[_R^^]__`bbS_cVbbacf1eegf "ee0 j8ijY8k\jjikklnn]ko`nnmooprraosdrrqsvufhvvwfv|{zz|xzzyx54}}~5}}y~i~lmptq                #" %$##% #'&)(''") '+**,+&(**&/..0/+-..+32243/122/769877:9!7=<%?>==6?%=A<@)$AAB)@CAE-DDFEBCDD,BI1HHJIEGHH0EL5KKLIKK4INM@PONNQP@>NTSFVUTTMVTFMXWPZYXX[ZPRX^]V`_^^W`VO^faZfbafdbZ\fhg`jihhaj`Yhnknlknnpooqpkmookt!ssutprss px;wwyxtvww:tm~~~~lrqvuzy}|LL335{||            {{}99;?  ?8' &&('&*(&"*.)--/.21--(263,643.06,)68779835773<;>=;8:;;8@?BA@@<B@DCFEDD?FDH0GGIHLKGG/LN5MPOMHJMM4HR:QQSRNOQQ9NVU>XWVRTVV>RZYB\[ZZU\B=Z^]F`_^^Y`FA^b%aab aa! dTccedhgccShji`lkjjml`[jpfooqptsooetvupwnvyxvvpyW~~udf~~Xd|IJ|g;;=AC=GC         #"#%$$&%!$$) ((*)%'(( %-,,.-)+,,)103211-315446502440988:957885<=<<9=<?!>>?@>> @B'AAB?AA&?D+CCDBCC*BF/EEFDEE.DH2GGGFG3FJ7IIIHI6HL;KKLJKK:JMMMLM=LOOPRQOONRTSVUTTPVTXWZYXSUXXS\[^]\\W^\` _ba_[]__[dcced`acc `gflihgkjgglklk`mjllnm`^lqopprqY[ppnYthfugttvufhtyxr{zyy\{r[y}|d~}}udg}x|RxR|~zeNe{NvhvqoV~Vxb]bxzHtuWWX            &"!&$"&!&(')((")'#(-*-+*/.--/10321*,11*54 765507 598$;:994;9$4=,<<>=A@<<+ACB3EDC=?CC3=GF7IHGGBI72GKJ;MLKKFM;6KO?NNPOSRNN>SWTEWUTOQWWEOYXI[ZYYT[IDY]\M_^]]X_MH]dVcced`bcUQchZgjiggdj[Vgmllnmqpllqsrrtsmorrmwvvxwsuvvs{#zz|{wyzz'w~~{}~~!{   c ` nnttoyx}|y^ \                  !   #""$#'&""')((*)#%((#-,,.-)+,,)0!10-/00!-3%22437622$79+88:93588*3=/<<>=9;<<.9@@A=?@@1=C5BBDCBFD46BI;HHJICEHH:CIKMM>IN?MMLNPPQNOPPANSRDUTSSVUDGSYKXXZYRTXXJR]O\\^]Y[\\LY``a]_``Q]cTbbdcgfbbUgi[hhjicehhZcm_llnmikll^ippqmoppamserrtswvrrdwykxxzysuxxjs}o||~}y{||ny}q}ttvzzu~~{J L J O Q O S U S W Y W [ Y QR[\           ! !"d#e""#"('*)(($* (,'+,,-+.,4 33540233!087#:9884:# 8<2;;=</.;;1/@6??A@<>??5<FC:FDC@BF:6FH>GGIH+)GG=+LBKKMLHJKKAHPEOOQPLNOODLTJSSUT*&SSI*XNWWYXTVWWMT\R[[]\XZ[[QX`_Uba``%bU&`gZffhgceffYck^jjlkgijj]gpeoopnoodnriqqrpqqhptmsstrsslrxwvzyvv]zvu]~}||~|x|{x~~ygzgy          ! ! #"%$##&%#)(+*))"+)-,!/.--(/!-10+321143+$176/987709/*7=<3==>35=@?9@?<@92@BBABAB&C;'CCCA%'DD4%E5DD:EGFbIHGG7IbeG?iPQ8PPMQPjMSRIUTSSCUI9SZLYY[ZVXYYKV]6Q^.]]_^QO]daUdbaPRdUEdegiiWejXiihjm\llnmjkll[jsqputssmup\sv,^w vvxw^`v{czz|{\^zzb\k~~~~honkuuotwwytt|k}kmprZpNLNqrfg           ! !#"%$##%#'&)(''")'+*-,++&-+/.10//*1/325433.53769877297;:=<;;6=;@">>?@>:?>:A@@d Ae @@CBBH CAI BBAEDD, EC- DDCG FF GE FFEI HH IG HH GKJJ KI JJIMLL MK LLKONN OM NNMQPP QO PPOS RRTSRQTR!QW$VVXWVSXV%S[(Z]\ZZW]Z)W_,^a`^^[a-(^cb1edcc_ec1_gf5ihggbi50gkj9mlkkfm94kqn=qonqjo=8qrrsnprr?nuUttvu ttT yYxxzyuwxxXu}\||~}y{||]ya}a\ee`iidmmhoolqspspw v {zw~ BDB fhf           ! % $$&%$$ )((*)%'(( %-,,.-)+,,)10021-/00-544651344198;:885;85="<<>=<9>!<C@#CA@=?C#"CE'DDFEDD&I+HHJIEGHH*EM/LLNMIKLL.IQ3PSRPPMSP2MU7TWVTTQW63TYX;[ZYYU[;7Y]?\\^]XZ\\>XaB``ba]_``A]eGddfeddFiKhhjieghhJemOllnmikllNiqRpsrppmspSmutWwvuuqwWRuyx[{zyyt{[Vy}_|~||x|^xgfkjgonrsowwr{{v~z5~u v  u             454 @B@aB               25.1 *- &) "% #"#%!$$&%!$$)%((*)%'(("%-),,.-)+,,&)1-0021-/00*-5044651344/19588:9578825=9<<>=9;<<89A@:CBA=?AA:=ED>GFEE@G>=EEHHIHDIBAHN3MMONMJO2/MVUGXWVVTXGBVYIZYYUZIFY_^]]&_`']]\`dcbb d_!bba_fe[NPff6N[7fhLgjiggkjg\knPmmonhimmOhsrjutssvusjvypxxzyrtxxor}Xq}|Xy{}qp}~kak^w~vl#" ''"##  !!  V  V F G  7&7'' "!  " $#&%$$&$-*-+*--/..0/*,..*52"532/15"576&987729&!7;:(=<;;6=(%;?,>>@?>>+C1BBDC?ABB0?G4FFHGCEFF3CK8JJLKGIJJ9GO<NQPNKMNN=KSARRTSRR@WEVVXWSUVVDS[ZH]\[[W]HE[_^La`__Za_LZcbQedcc^eQMcgUffhgffTkYjmljgijjXgon]qpoklo]Yosrautssnua\swveyxwwrye`w{dzz|{z~|vxzP{}b{<O::<||}~yxysx          )        '    '  ! !###$"%#''(zx''&z))*yr))(y+"*,%++q,*r+.-.&ik.-_.0/-00]-/E01 /11C/ 1543365#%332#*$889*;:882;2+<<=2?><<9?L1@AK@CB@@=CbDD6bE7DDEIHGG{I,sGGF,KAJKAI}KKJIM7LLLLEO4NNNM57NQ:PPQPO;4PS>RRSRQ?:RUBTTUTSC>TWHVVVUJBV$%XXXWFHXYZYY[ZY]Z^]]_^Z\]^`bb^cbbaccdffcgffegghjjgkjjikklnnknompnroqrrsqtrstvvsvwuxvwxzzwz{y|z~{}~~}~{}{uSuSJ_`u stsadghcdceh!"UV}}iiD$@uxCqpq?@?mlmy|\ [ihmmi        &$# "&yt&&#y)(#%(~x(('~ --+--,  //, /,./54325563577 1 7741996998;):;;:;=$<:%==:<=><?>>!?<">A@?AA?@ABC BBDCBFDGEFFHGFJHKIJJmKrJ  LMWLLNMCELPNGQOPPRQGIPTRKUSTTgUKlTwfWWXwZYWWVZ[ZQ\Y[[]\QS[`]U`_]a``Uf`aXbaacbaXccYdd ce d\^dfe_ ff[ _`fjihhjhkhgkklnnkonnmooprrorsqtrvsuvvwuxvzy wxzzz ~|{~~{~|})_aU_XrtjjAutuA.SqpqS@ammaR^kL\:{Jfiejfj0A-pMaterial.002 8  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~                           ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~        !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~                           ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~  !!!!!!!!! ! ! ! ! !!!!!!!!!!!!!!!!!!! !!!"!#!$!%!&!'!(!)!*!+!,!-!.!/!0!1!2!3!4!5!6!7!8!9!:!;!!?!@!A!B!C!D!E!F!G!H!I!J!K!L!M!N!O!P!Q!R!S!T!U!V!W!X!Y!Z![!\!]!^!_!`!a!b!c!d!e!f!g!h!i!j!k!l!m!n!o!p!q!r!s!t!u!v!w!x!y!z!{!|!}!~!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!""""""""" " " " " """"""""""""""""""" "!"""#"$"%"&"'"(")"*"+","-"."/"0"1"2"3"4"5"6"7"8"9":";"<"=">"?"@"A"B"C"D"E"F"G"H"I"J"K"L"M"N"O"P"Q"R"S"T"U"V"W"X"Y"Z"["\"]"^"_"`"a"b"c"d"e"f"g"h"i"j"k"l"m"n"o"p"q"r"s"t"u"v"w"x"y"z"{"|"}"~""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""######### # # # # ################### #!#"###$#%#&#'#(#)#*#+#,#-#.#/#0#1#2#3#4#5#6#7#8#9#:#;#<#=#>#?#@#A#B#C#D#E#F#G#H#I#J#K#L#M#N#O#P#Q#R#S#T#U#V#W#X#Y#Z#[#\#]#^#_#`#a#b#c#d#e#f#g#h#i#j#k#l#m#n#o#p#q#r#s#t#u#v#w#x#y#z#{#|#}#~##################################################################################################################################$$$$$$$$$ $ $ $ $ $$$$$$$$$$$$$$$$$$$ $!$"$#$$$%$&$'$($)$*$+$,$-$.$/$0$1$2$3$4$5$6$7$8$9$:$;$<$=$>$?$@$A$B$C$D$E$F$G$H$I$J$K$L$M$N$O$P$Q$R$S$T$U$V$W$X$Y$Z$[$\$]$^$_$`$a$b$c$d$e$f$g$h$i$j$k$l$m$n$o$p$q$r$s$t$u$v$w$x$y$z${$|$}$~$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%% % % % % %%%%%%%%%%%%%%%%%%% %!%"%#%$%%%&%'%(%)%*%+%,%-%.%/%0%1%2%3%4%5%6%7%8%9%:%;%<%=%>%?%@%A%B%C%D%E%F%G%H%I%J%K%L%M%N%O%P%Q%R%S%T%U%V%W%X%Y%Z%[%\%]%^%_%`%a%b%c%d%e%f%g%h%i%j%k%l%m%n%o%p%q%r%s%t%u%v%w%x%y%z%{%|%}%~%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&& & & & & &&&&&&&&&&&&&&&&&&& &!&"&#&$&%&&&'&(&)&*&+&,&-&.&/&0&1&2&3&4&5&6&7&8&9&:&;&<&=&>&?&@&A&B&C&D&E&F&G&H&I&J&K&L&M&N&O&P&Q&R&S&T&U&V&W&X&Y&Z&[&\&]&^&_&`&a&b&c&d&e&f&g&h&i&j&k&l&m&n&o&p&q&r&s&t&u&v&w&x&y&z&{&|&}&~&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''' ' ' ' ' ''''''''''''''''''' '!'"'#'$'%'&'''(')'*'+','-'.'/'0'1'2'3'4'5'6'7'8'9':';'<'='>'?'@'A'B'C'D'E'F'G'H'I'J'K'L'M'N'O'P'Q'R'S'T'U'V'W'X'Y'Z'['\']'^'_'`'a'b'c'd'e'f'g'h'i'j'k'l'm'n'o'p'q'r's't'u'v'w'x'y'z'{'|'}'~''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''((((((((( ( ( ( ( ((((((((((((((((((( (!("(#($(%(&('((()(*(+(,(-(.(/(0(1(2(3(4(5(6(7(8(9(:(;(<(=(>(?(@(A(B(C(D(E(F(G(H(I(J(K(L(M(N(O(P(Q(R(S(T(U(V(W(X(Y(Z([(\(](^(_(`(a(b(c(d(e(f(g(h(i(j(k(l(m(n(o(p(q(r(s(t(u(v(w(x(y(z({(|(}(~(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((())))))))) ) ) ) ) ))))))))))))))))))) )!)")#)$)%)&)')()))*)+),)-).)/)0)1)2)3)4)5)6)7)8)9):);)<)=)>)?)@)A)B)C)D)E)F)G)H)I)J)K)L)M)N)O)P)Q)R)S)T)U)V)W)X)Y)Z)[)\)])^)_)`)a)b)c)d)e)f)g)h)i)j)k)l)m)n)o)p)q)r)s)t)u)v)w)x)y)z){)|)})~))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))********* * * * * ******************* *!*"*#*$*%*&*'*(*)***+*,*-*.*/*0*1*2*3*4*5*6*7*8*9*:*;*<*=*>*?*@*A*B*C*D*E*F*G*H*I*J*K*L*M*N*O*P*Q*R*S*T*U*V*W*X*Y*Z*[*\*]*^*_*`*a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*w*x*y*z*{*|*}*~**********************************************************************************************************************************+++++++++ + + + + +++++++++++++++++++ +!+"+#+$+%+&+'+(+)+*+++,+-+.+/+0+1+2+3+4+5+6+7+8+9+:+;+<+=+>+?+@+A+B+C+D+E+F+G+H+I+J+K+L+M+N+O+P+Q+R+S+T+U+V+W+X+Y+Z+[+\+]+^+_+`+a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+r+s+t+u+v+w+x+y+z+{+|+}+~++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++,,,,,,,,, , , , , ,,,,,,,,,,,,,,,,,,, ,!,",#,$,%,&,',(,),*,+,,,-,.,/,0,1,2,3,4,5,6,7,8,9,:,;,<,=,>,?,@,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,[,\,],^,_,`,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,{,|,},~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------- - - - - ------------------- -!-"-#-$-%-&-'-(-)-*-+-,---.-/-0-1-2-3-4-5-6-7-8-9-:-;-<-=->-?-@-A-B-C-D-E-F-G-H-I-J-K-L-M-N-O-P-Q-R-S-T-U-V-W-X-Y-Z-[-\-]-^-_-`-a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-{-|-}-~----------------------------------------------------------------------------------------------------------------------------------......... . . . . ................... .!.".#.$.%.&.'.(.).*.+.,.-.../.0.1.2.3.4.5.6.7.8.9.:.;.<.=.>.?.@.A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z.[.\.].^._.`.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.{.|.}.~..................................................................................................................................///////// / / / / /////////////////// /!/"/#/$/%/&/'/(/)/*/+/,/-/.///0/1/2/3/4/5/6/7/8/9/:/;//?/@/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/[/\/]/^/_/`/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/{/|/}/~//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////000000000 0 0 0 0 0000000000000000000 0!0"0#0$0%0&0'0(0)0*0+0,0-0.0/000102030405060708090:0;0<0=0>0?0@0A0B0C0D0E0F0G0H0I0J0K0L0M0N0O0P0Q0R0S0T0U0V0W0X0Y0Z0[0\0]0^0_0`0a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p0q0r0s0t0u0v0w0x0y0z0{0|0}0~0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111111111 1 1 1 1 1111111111111111111 1!1"1#1$1%1&1'1(1)1*1+1,1-1.1/101112131415161718191:1;1<1=1>1?1@1A1B1C1D1E1F1G1H1I1J1K1L1M1N1O1P1Q1R1S1T1U1V1W1X1Y1Z1[1\1]1^1_1`1a1b1c1d1e1f1g1h1i1j1k1l1m1n1o1p1q1r1s1t1u1v1w1x1y1z1{1|1}1~1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111222222222 2 2 2 2 2222222222222222222 2!2"2#2$2%2&2'2(2)2*2+2,2-2.2/202122232425262728292:2;2<2=2>2?2@2A2B2C2D2E2F2G2H2I2J2K2L2M2N2O2P2Q2R2S2T2U2V2W2X2Y2Z2[2\2]2^2_2`2a2b2c2d2e2f2g2h2i2j2k2l2m2n2o2p2q2r2s2t2u2v2w2x2y2z2{2|2}2~2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222333333333 3 3 3 3 3333333333333333333 3!3"3#3$3%3&3'3(3)3*3+3,3-3.3/303132333435363738393:3;3<3=3>3?3@3A3B3C3D3E3F3G3H3I3J3K3L3M3N3O3P3Q3R3S3T3U3V3W3X3Y3Z3[3\3]3^3_3`3a3b3c3d3e3f3g3h3i3j3k3l3m3n3o3p3q3r3s3t3u3v3w3x3y3z3{3|3}3~3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333444444444 4 4 4 4 4444444444444444444 4!4"4#4$4%4&4'4(4)4*4+4,4-4.4/404142434445464748494:4;4<4=4>4?4@4A4B4C4D4E4F4G4H4I4J4K4L4M4N4O4P4Q4R4S4T4U4V4W4X4Y4Z4[4\4]4^4_4`4a4b4c4d4e4f4g4h4i4j4k4l4m4n4o4p4q4r4s4t4u4v4w4x4y4z4{4|4}4~4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444555555555 5 5 5 5 5555555555555555555 5!5"5#5$5%5&5'5(5)5*5+5,5-5.5/505152535455565758595:5;5<5=5>5?5@5A5B5C5D5E5F5G5H5I5J5K5L5M5N5O5P5Q5R5S5T5U5V5W5X5Y5Z5[5\5]5^5_5`5a5b5c5d5e5f5g5h5i5j5k5l5m5n5o5p5q5r5s5t5u5v5w5x5y5z5{5|5}5~5555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555666666666 6 6 6 6 6666666666666666666 6!6"6#6$6%6&6'6(6)6*6+6,6-6.6/606162636465666768696:6;6<6=6>6?6@6A6B6C6D6E6F6G6H6I6J6K6L6M6N6O6P6Q6R6S6T6U6V6W6X6Y6Z6[6\6]6^6_6`6a6b6c6d6e6f6g6h6i6j6k6l6m6n6o6p6q6r6s6t6u6v6w6x6y6z6{6|6}6~6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666777777777 7 7 7 7 7777777777777777777 7!7"7#7$7%7&7'7(7)7*7+7,7-7.7/707172737475767778797:7;7<7=7>7?7@7A7B7C7D7E7F7G7H7I7J7K7L7M7N7O7P7Q7R7S7T7U7V7W7X7Y7Z7[7\7]7^7_7`7a7b7c7d7e7f7g7h7i7j7k7l7m7n7o7p7q7r7s7t7u7v7w7x7y7z7{7|7}7~7777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777888888888 8 8 8vkmark-2017.08+git20220909/data/models/cube.3ds000066400000000000000000000012731441550741700203350ustar00rootroot00000000000000MM ==_Materialcrat fff  0 crate-base.b@FCubeA;A????????????????????????????? A      0A-Materialcrat  @A???????????????????vkmark-2017.08+git20220909/data/models/horse.3ds000066400000000000000000004302421441550741700205410ustar00rootroot00000000000000MM0 ==0FMaterial.001 ```  0 @F0HORSE_L_MateA30A R;?: ^ =)?ϊ>)+?>/)?){>Q0}'?wdk>Z4%?Aq>n%?fi>r!?d!x>ȊW#?>  ?z>y~"?s>~t ?{|>QĪ"?D>Σ(9!?>D!?r>Z!y.?>$- +?#>χM*?F>t},?m>Cսv(?L>Y&?=>V}%?G>j$?8>zf#?&>%#?>6$?2>3;.#?\>:A#?\I>D"?{>Cu%?D>>Xi%?:>{b%?>5"J'?׬>'?0>lgIl'?m>dX'? ߓ>)/C&?HĎ>b; &?w><<<&?ψ>Z<#?>nU=S$?J5>1O=Y"?!X>@)?)ə>M+H,?Ҝ>]y*?'>0R(?>1ļ+?v>ۼ)?>M+?N>G)?>׮;',?>l<)?=><-S+?>C=y(?H>`@<^(?> ;j(?LR>!=!?z>Ҽt"?Iz>.!?m>9H\?Db>Iν?b>6 ?1^>&!$?_,T>h0?N>ϻ4?Q4>k 3?K>b-?N>ڽe-?>p+?a>g$r*?n>PнT+?w>n-?HX>ẽ!,?̎>у.?>^ѽST(?\>ź'?->3(?Xڟ>إ ,?>T}R+?r>4ͽ`*?]>5Nd*?)!>~t/?>:1?R>,/?^>7ּ/?'L>T![/?av>=8/?>b==-?>+><2?3>m;=p4? ;>~l;?V4?>R3w2?ȗ>[2?3>H>e5?s>5?>>|s02?}~>نak4?<>o 2?ݠ>uһ4?>oO9? >kШD8?>Ϳ 8?L >W8?f>;L8?<><^6?J>rv=.1?P> = ?TWh>"-;$|!?3h> fv?9D> ?7<>y?P>`nR?i>LyٽK?`H>v̽ٿ5?sU>h˽p2?j;>Ƽ-?ۍB>>)?E>v=+?Q=>}!O0?+C>>Ap2?sb5>Zf.??5>& 5?7>ӕB1?U>>:.?5B>=3?!?>zz<4?3,>!LQ7?*>W+1?P_,>ݽ2)0?(>ZνE,?`4>#p*?.> l/?Q(>J*?E(>k'45?'>5?%!>F42?>X+W.?>U,?>&*?Y=X.*?>o~G'?>7߫`&?"-5>8׽#?T,>Pf(&?5(>?)5 ?1>ɭv=!?g=>Mj}%?в<>~d$?_,J> ?j&X>KM?mD>Tպ?/6>V?ƕ4>"?>`['? ='?,=.\I%??=\5"?>!= ?> ?>i#?{t+>P$t"?6>Y?ݮ>O_4"?}\>=$?TR>i (?FJ>kK# ,? E>?<`@.?)@><#1?e{@>q, =,??>?=30? =>45? >VNǽr4?X>齬W3?QB>Z1?>FJ4?e>g+7? j >.Y8?C>bz :?X >i*Ee:?t#>yi<*?aF>w0< &?EL>l=)*?ԬC>`> <&$?jU>K<8!?q]>)<&?I>&<"?"T>c= ?SY> ?= }?m>Qo;?>L::?ѓ>ھ9M=̒8?(>%]< =?u>A={p?=W~=?\р>*,;B=?>2??\>0b@?1>;B=?>g,4=1 Z@JJ=:?a>~;6K\=?1>_@?6]>@^C?m>YB?x>IE?^x>6;^x??x> <=?v>Ez==M=?hu>q$nC?o{>h$F?c>xGF?>DmH?>頽̡??V >|돽pK?>X)K?F>Ǻ J?>YɽM?=ʄ> 2M?k*>;vM?@o>ֽM?z>?2CM?#y>鞱MJ?)\l>lQI?h>5K?r>>zH?.ph>I?s>[maH?^y>S5F?\>v#E?m> 6H?_>Zf@?+j>U$jB?Y>:>?yj><(:)=q;?j>g̽яE?>sI?> ;N?B>pŽ C?˔>(CF?x>W@D?j>` hH?R!>G GJ?v>L?!>>HM?z>1fL?©t>D I?o>%ZJ?xp>U$\G?Y>7F?g\>3Q'F?p!R>c`D?O>| sA?E>ToGk??7N>b|C??zX>ezwMC?/@>cA?/1>/>?Gq+>&R??(>nu'>?>>zۼ;??>:E>~=;? Z>A?GK#>lĮC?7>B?$2>>- B?K9> zFB?;?>܃㽰@?>ٔؽ'>?fH> r??+ >ͽ;?f\ >O>;?J\%>=:?s">XŽG8? >_(M>?,>Տ ?? >kA?>νEP??ߘ>+y rF?>ѽ?Z;?a>q[+>=? >8?F/>bB7?h>2?@J>%`-;?5>@?A>Q'+w8?[>zC5|54?ȥ>A'n>?V> 6u;?uD>B2-A?肬>m9?q4>`*d8?گ@>mL9?}U:>>fL;?D> $p=4?5F>:sC?Ӕ>5p^B?_>gY.D?Ɛ>ȞWۢE?I>m^kG?ʼn>lP=?>WΒ@?f> 1W ;?{o>Gm,9?)ђ>v\j7? >m|^5?{>vMk3?P> W'#.?F?>iFI.?8>@'51?>LW^1?j>vXY2?j>l[5?إ>\f;?>V}L??A٪>LTA?>C=B?_Q>mBB?k >@E?>FBF?Ȕ>2CyJ?dJ>K=.G?ۚ>6E?>_b7VMI?A>3WC?X>NfMiC?w>jEA?/N>YA?>^5=?<> e^7?͟>KD?D>TSE?>=ZC?>6\Y>?}>ȟPwE?>?VE?-I>XpC?Gk>QSoD?ep>ZVZ>?l>W$ 8?>G0?>w[5/?͗>(nC?sr{>Z.uG?D>(2asB?jg>KS+E?+o>QI?s>JTB?^>tFH?9h>J6G?'i> BL?ao>$h2J?r>*M?>,=vN?Cs>>JS?!>]VS?>vUM?p>\N?/>ެ]R?>:]̥N?>WXOȍM?B>8fL?>MZS?}r> MvQ?>|D?gR?}>RFU?>qJV?>XX?>[V?l>RR'W?'>#QX?E> S)Z?9>V5Y?g>8e$-?>*ܳ-?>[AҸ,?y>4,?><{15/?a>+p-?PΤ>k&/?>KX5?>M=;?&>{^GK/?j;>V-?e>%,?>)l4,?ᗞ>=@%n+,?{>>,?!>L+HA?0>A*;?u>=[??>X'/E?`>!lQI?ܼ>gL?'>M?>-^N?<>R[N?>v*WM?fˉ>W-zaM?>=:,M?> -AJ? >my+G?i>8µP?ѓ>!BVZT?>Iu:W?}X>1O?鑈>@R?)>PKV?uԗ>~A#Q?y>k$J?a9q> ) F?. b>se:B?p?T>qf. B?G>ݒ;K???>6JF@?L>̗??u >L>?>0 =?k>)Z^9?o>'9?z>ك=?a>$wyC>?PU>ŗw^9??P> E7?ᗉ>ɔI 9?> $06?z>AC4?>*o4?<]>t-?K f>ߞǺ/?~M>NM'?3wb>7=&?ABG>.=%?}9>P㠾+?09>u#?->!j*?->"K? >ZßC"?>E"?`5 >3蘾/?*>|4?jL>h:?tj>U:y8? C>1'5?(>.?>p9&?8>#d;? A>3ǁd8?B>VZ4?5Y>"3??R>~X`1?>t~.D-?8>&5?h>;2?JQ>c3?>#2?->5#0?|z>C91?o>!/?c>jwM1?o>oʤѷ*?{>0""?'p>"?.b>󰈾K?TW>On?N J>,E?6>5?c0>żh?PK>[w?nd>wן"?iw>q'?Rڂ> 4f +?#>,?o>%?m]>-(? f>ċC? Q>Uu ?1<`>鞡?|=>;OR?B>㣾{?H}<>R'P?D<>?;A>/??(6>Š?<>ಾt? @>G,?Z<> ?> ?g(>`ݦ?>=?=t?'>?>=?=U\?3r= Cz? =f.?=j)x) ?Pd=}!?f=i&?2=i?R,>z?D%>$.?|=_vn)?!s=~E3?h>ÿly#8?W0>a[=?(L>rK:?j;>x@;=?<><V̂7?Y.>:a2?|>es-?6=I,?=G4?lY >T@6?C(>69?G .>a66,?o=<;A0? > X?B>> Bl? %>.1h?I> I?Dc?>3o?j6,>Q6%?J>TA!?F>a\ !?a>Z(?#}>== *?`u>$>'?9p`>γ ?>]LC?58>j ?ߊ=D0]?c=ሎO* ?5= s `? =K|Q?P{U=>a7 ?g}*k >:=˾cv A> ҾnR'>ɤ׾.@>&ݾ#8'>⾼5P=>ncP> n\:>lⱍ=վa =[>f̾Y{t>uǾ3N/S>8E{H[>tѾԄ6>)۾Bz>վbU:C>]b'>>p{ i>)JN>q +/>V7w>ҽb=udGӤ=w*4z=xK쾥"!~l=&=@\A>f~>;"=ۯ=d=BW=#ھ4#Х:<&2=⾝+"ɾV0P>ʾ kO$>ȾvfnC>Ծx>㾏 {)>R"޾^^>4Ҿ\i>>JǾ3> [>ԲZƐ>\Ц>ľjǘO>}e>.|~><9oD@>ngM>7KCI>>WD}4>¾1+ϑ>Ế&$>G T> q?B>Ƅ2Vݩ>jt\q>o. %> i>NwUt>V5>5PmX>w+D> q:>"B>I1Ǥ>b>ɽ%>W>Ο> gBm1>CuN> BqP>PQc!>ĐUd'>"t*y3>Ȣn3Pb>:;>t9u[1->z}Zf>mf`b>~Zk>dn#(>R>#q¤>>ƠEZI>塾b~د>p>bfgi>菾>Ë>*N>bw>ţPڃ>DȾ-'>.Ȝs>î|NLX>ɬgE>H > u#=HS=wGO1>R;W=5#as=Ҿ)=,%R=!>}ڱ =׾ 1=H⣾Vo=Oݾ:n=f%٪. ''b=־SD о%gо1ξpP=` پp ;H={Ǿo}= þB=G+=R̾N/ھӛa=OӾ73ྜMѾ>M>8^$>׾Pؾe=j*ξV߾=Ož侉D=ž&=fо ھI=x۾Mо^t>õ澗Ǿ>ZO4>R辎ū!B>׾yC>n־¾|->;)ɾ淾AT:>ͽ ܆+>6ʾ ˾7>+U¾KӾ > Kھ^=-T0Ⱦ>۳ľ>Ig>bG_H{;>`鲾;(>.Ǫw@E>ξQOL>PξCl\>7㾃߱o>):P>,ZquPw>3Hqo>,'Y>{׾ e>`۾\>龛Ė̑>Ǿנf>sMPW>1c%B@>^ _)r>>n]qu>M2Ǿf>ž'-`q>Aξ\O>9^>٢ȾBUo>b¾%>h>ƾDtp;mS>\/]y \> >䔾H>Ƥ۷F۠>,GȾ>A}6ƾ7>bKž I>ۭ>[þ>Gɾ׌>7Ͼbш>'Ҿ‚>9w־^ >;ξX> ˾V>M8Ͼ>%uԾ}> c} =9I(=+>;?𮽡><?NŽ>?߽>[?Ͻ# >"?Kp >]#?ؘǽ>Q?X1>??fV?<>J 1:?SK>6! ?. >%?0j>PT>X?5 #)?ʕ?:]> ?^0Y>9h ?O?а?F]+ ? =>J1>C?(>! ?ͺп>D?ѽȵ>?]Ž>?}?>[?r)> >/@?D?dֽ ? >[콹?R>MB>>ý-(>X>>>=>6 ?+>C?tR$>6? i >t>UL;>9> ?>wM ?x>$@.J?s>k*?E>A?Dr>w?> ?k>P?;B>?R>v=t?u>H7?a>%(l?> ?-U>وz?1>!?dR>z?=>pS?>5qD?]>c)?1> "d?gk>aU?*>^>&?0>vJ?hݽo}>K?y὞>>S佣#?>fLS?L>\?:+>V?OU>9-?#>u?މ>1?0>y*?'>9FV ?#Z>ҽ>?x>u$>bʽ;?s>*{ ?>ѽ?t?Y>k ?¥>E?MА?P>Qؽ? ?G>J̽Z?|>>+?2 >9?./>MJ?*e?>VA>?-Y>C?ǽU>W>YN>%>snT6>X:>/sE>i>Mި>h^>㼽j>Lf>W^>/>;9>Ǘ>>>̲Ř>NӇ> 4>11>j>TL>Ž2>Ê>x' >>4pPP>>W>ٱ>нY>>нc>'>>!>r.Ƚ>/>ؽ`.>y>Wk>}>;v>y>)[k>l,>1v}> >0j>8>GR>8>>љ>rL>>ի9>>s>y>F'`>>K^o>ä>LYI)>g>|cH>N> iqS>>h_>> KM>#>tq@9>3> 3->>WR#>K>P0/lS>1i>eW0>M>!CK\>>4;5>Bu>֞<~/a> >R>5Q>Eڻk>>bҼz>o><Р>>IԿ>a>%x宇>a>\;&1>?18b>̾ ?J>v>߼2>sk>>1>R>>,M>h$>$=>>򶒻1J>7Ơ>K <> #>ۺO>>V >ݍ>̼rc>ņ>. >_>3m>8>fs>>mOf>|>`+Y>y?Eu>V>V$Я>\>YÂ>+~>t)>|%?ݾg>=?LG>?Ă;>N>/>Є>cⰞ>;>=> >չ>{8>>V+%=i>^U=l> {q7=+>Uv=+r>҃\q=V>׆=d>򋏾=>m>ńfx>hj5_<[Ug>eb|Nk>oM^p=u>F_w͉>Dj˒{$xɼH>[m^ =&kd>ˢeF}=8 c>ef=.i>rҁ=>W~ݪ~0 jc/'㴽iȺ#t 57YrtWz5"L;Jo&2"=QJ=*#D9 ,`Խ,l"/ν56*y9Oץ>=^Ƚ/ܽ WĽ#S 8֕z½.A& 9a]-罩$>}k .  3#ŎiR/& E4"f1 ېC:FՔID;RQa<91\H tΐ= [Ub<1Bj_!js0ngC]FPXYD \r@~rI#ZFAwkMC8N鏾EUU+q~`wܠ2=rὯF`ݢ+=.|(J=PS2z='b< <u=W=Yý<=ˆ=Pt݃:L>槽><ĽM!>ŽR=ҽ >'{=:}7>l~=V̰e.>E= 🽁H,y=v45=Z3S=&=:s=J==/q a=vI엽Y|]>ߑ|x1 >H7, >@J q3] >oM9%Y >9HT=rڕ<%=Ƭru@;R=_yM> 6wP>'Itk >xJ= ~'H> b ~ >gX=zソ7<ӕ=ꠂ=w==Y0--d=p=1G?H =@C= Ml=| >=J X=m=d =~%=~1w؉=Kz=?l4==8M&<c=U9;X[<>6>n8@G>~=ҟĽc3>=iy5>С=>y=:u>R41F=*= >Ah= >L = >oT+-<2>VYˍ^=*#>!U<1>so\Z=:>ZXG]g=_K>s?aY=ȸP>PU=-c>$fc=oo6>]6NO=a4>h*CM=m>}G=>+R;U >>)>N0>6 >QR>BV>g>QΟ>jL>7 >/>D/>o(>9)#>vXG>k0!>a>>y/>'>B#6>|>E 9B>'>"A>>ѝJ>+>XH> >ؘS>a'>dPeR>Y>1Jҽ[>Q)>ֽTm&>g>3;r<>jH>"oF>k>+'45>rg>]d2TW6>`>W+!>Y08H>ό>Z85>je>9aE(K>_G>b%kvQ>Ei>:AT>>R>J>Z_.]>i@>hGZe>7E>ڽ b>_>~n>pc>y߽oX>| l>z g> >]>>^!Mt>+y>\(>d.>ӽoCu>=#>E`>n><\[Z>1s>SYa>1>Tt>k>x}h>^L>+&f>V>͋~P's>i>煽h>e>84z>Tet>uP>Xu>S|>y>+>>c1x>i>Ϯ]>>7So>g>X^>]>fTVS>Ԧ>zI>V5>G O>n>G8H[L>,><@D>L6>ї5U]>i>oL>>ٽ# S>5>NA>>!ڞ<>>\pD>> />F>Ü2ɻ=e>fy>>b3>5w>Hb*>s>|>>Z N>s>f4@PRV>ϯF$1>;D9>ƽF/ PC>ܽ3ὺ;Q>սf-[>;mʽ3b>p i7aW>+m(^G>:OuBLTO>zGE"<>ee@/>ry+'8>3Z|X|=.dLޥ=R"5ފ=.C=<NA==IK=##DŽ) =l=J^od=1H0y.=Z^但VnRI=|k=ݽdKa=cfb<=j3p/*6c9cN8'Ľg[Hxp#Z Bƽ HkSq1WwW?=ΜhG6JiÂR ]qbjDD]3pfc_M/[wV(򈽁쁾c1@Λ|)<t kܼy2vb. 퍽5p7XD,rAM#O7PW=i? >9w~} >vo9J=8=oW%>%Ӯp>/ֺl]>!c'.>*O>ʽ \>c>]p>I}J$>Y02SC(>_c&A>ߋ+:>Լ9f,>ʼA{->ڶM@>Ҽ zK%D>(HC>;i>**>.>o\>>ƽⰾf>h>2mA!>͡v>]m?#Iv.> >ýTʾy>/ǵҾ>@ þ">CS῾ !>Ҽu귾->. c]ɾa/>2cξ=$>IJmҾN> ǽsܾ>6¡>ʽv>eCU#>ɨ; >lh۾(>m0еPe6>ȢV`4>r;)*>vԾ9 6>7[W&> ý58$>#vf5>PKԺ[1>A`ľ9>>|e3;>-Q+:>)NZ<>>`\aԾC> nkC>(|DvB>mqp>>hyhk,N>^J>?D㾹4U>ԾyS>I6ƾWS>UhN>?XE>jļFN>0<РX>e;ꔾ^>G +X>KzɼM =JI>&sV>DMA苾#Od>,dhR>S33yKo>>̽t>Dt9g>,Mrd>4[FRվo>^hǾ/f>5Zžn>n$GҾhi>Flܤ־qb>qܽ#ѾLn>*jֽ~þ8s>ڽK>v>ɮc{>7J>>iD>qt{>/%0>IǾk<|> %Ծ%t>T@pg>5bl>N-l5*a>#8e>Nǽ"9^>床qa>)َ֗y[>N¡=bV>'̽+8[>&[>ѓ * 0g>\L6`ͦ29U7$lڽ~zLUq~LͣpR/TP#S&zTh&ؽ9(h $ \#rZ/Op𰽈Tz*ܽE\i6G ]jľΠ6fr@> \˾/\=vCx=ľZ=~clľ)e=,3<=\QE==?]#=oA=$dž5N=Ϳ=0x*=Wf=,klʜ=Ȓ=_v={0=Ty@=l=9w[<==3;Y>n _gκ=@YC >Yϼ="Τ;i=ףV̼ >UF>1-C=3[->j<2 >-P=3&'> vG&=ܺv[#F=֜B=rzrA=#=r=},>>Ee$>U*E)>xê_Ch:>'ELc9 >+T<ߏ={v P=+=]=B=G!==v5&c>]=!e$><=.(>%$;={+P2 >r[ 9]=Ė&{>I&@>n;J=5%2`ڨ=5bv =z;2{i=s2f҄=]mI~7bH=TN]y=\+`-=#ٱfh<$;dᗥWB%%>Jҿ$>*>+t>%>)3/G>\ K>6/`>- rg>lnm>9m C>`C+ps>Dѵ>l hPn>ʽ>ur#ue>L1-2-z>*U1h_>+cAH>#dA>+h:m>_>scE}'ʽ%>,C>#*7 `=5-$=&r/=uMM2=\8t>q=q `2g@=<H,=/seiU4=M%b_ξ0Њ >"׾ս U >@ž^s>IBy>Pb8>o۾꽥~q<3־$1ʽWdb]- Y>ľ=G;g>[Ӿ [ s>/Kq=B#=)_=8ļ=KO=,Q(=UƉ>;ʹ!>7>Ti(;G>3 w4Z>e>ս{t>p+$꽢>͒nUֽ8>Dҽ.>X۽>zo۽y;>;¯uv>w䚽8ƒ>K{֑ލ>盾:>5TN#ZFf>挾gC J>'W>c~g;}0>b pA^p6@=;K ><Ig==UMPgB=n>Ɇ_6<`%>3Dr܈;b<>_SU>bQkr>n%FQ>gYCCs>Ki}r5m>v*X\> Lvuir>Ո#M>I>Ndv>m'v@>:~dὶ>d1 #y>@bu;L5|>[sBdy >@;cDԎ>Kn-[>4Q>Vtc>`;@>8J䛰NK>OF5:LJ:;BE'=!>.=.> 9"=#M>f#i=3>%'==Xe=n >"=nN>uh=(>-)=)5>%rz<;@>,O>&;tּV>^9&n^>iE>X>/zg>̻KMRLr>%\=˿= o>ŗ=˽a, >t?=1,=ޯ=)䇽>=휫=c=q=>ʽL=&>uC=<>u=k=<>Tq E? =79>|UK>۽)<G>KvZr;ID>d۾ʻTwN>>3A[P> w?T>u ޟU> 21[>JS>)\ۄL> ׼ R>IMꁼG>#q9!=1>/V=~1>m5=16)>&X=`;%>[Uڀ=>,@ޚ=&>S/E =0> >IXS=>W=L6=ˉ޶=0=~^> >=4[O>{O=iռ_5>"=>g =1ŻN>t=NQ<&c >H=b:<(>V=›1>v=1<>GN>?=,?=S=Yuu= ><-= >5==M><7$=E= N99=}> 粋=!>C<R=Z> ;SE=#> =G>4=uG=l_>+= &:=(6<>==%>=W5=r>=1p=,>=b=Sq?>!=y=zO>=ɣ=2>%==u>ۨ=3= |P>=t=XE>=<=e>Zs==Z>'/j=0=;>=Oq=|> =={=b=M=g=T >=bj=>`;M<:.>O]==V>=D=>o=ķ=Y =ܬ== =N7= =#=#=(S=4=T=A1#=v==S=6 = @.Wv>!VC~>1%Qe><U½bp>P]1ƽ\^>]#we> X8%$w>#ֽH>D:&>U+yH>!N6̤֊>\@n>WfC뽠>B?(Ooq>XK ҽӬ>!S+1>_>]S-Z{> ('>"cA>;1H>*Fh>MYDZ>70$,>j>m ѽ9F>n6߽]1> M,hCB>ُ⠔| C:> gڼ:>.PPI!(I>ms;@>&=<<>]=^ז<>_G=$>/;:Fv21>S_50>)>tz>Ȇ<>{>^=U>'e>=]>N>T1=)*>S>2?<{>;G>`A=+>~]>A=uf=D>Ѱu=.=*->V]=m=>Dr=0=i=L2=H=*J====*?=9=K2==x>E >v=Q=2>4 j=H >*&>ܧ==W> 1>>06>~(A>2x:7>O`>@#<J>v>=@<|W= +=G;>a=$W > }<I#>#=Wk >j=*=H=V>==>=&= >2ª=P=k!>b=4k=E >x={=B>=Je=~=Sd"== ><̷y=ն= <5==M<=oz>Y=O=9?'>=<ȕ'>;=,<->e5=f;3> > 0>V=Q)u0>=Fd[)>=*8;P'>=`R>T< ==|Q;==xڻ}=ې=#s>,0\=!> I=!=={=ˡ==e<=[=(_m==?mk=. >2A=䛲:ν/">s..>g$7> k<>N)2>Fkc;%>guA^>'޽Z2y>SڽC~>}/=AiGr>ݠd@ .e>NAzW>ɼWR>JXDc>90Q*]>`7_h]>}owi>䌽Vot>R dy>d½x>8X˜ȣ>wzQ(j> z_zy>R>uek>'0pz-x>tĽ>y>.d4 vmt>y_\;>+to>bO j_uz>!U,r:і>˞$Fpכ>QB])7>DgΕ>e":%>_6K>Fn>tӽK">PZn>)XӰ>s.ׁ˙>;׊>*3 &>' ؎>>p"#>ʽ12/>p$}>%,f>$R*>}:ުwQ>}UT>Gb>8ټdLV>^t>!9ZF>3R>P->.">};<>,>^8Y>pO->^N>C>9_F> >sL>Gc%>5EK-A>!>GLK>D6>L^ F> ?>ɹV>O>MZ>B>UZV=>)>6C>G=Ç;>&=肽>7aѼW%fBo'=B'=)=@5j=zr=6=Շ<]ҽ=D*M=,JڼS=>!93s<,Լ1>W4=y>I9>=P;5>H8=1'Z?=cfB=r=ng[G=ǹ6;O"DмA̰ |®Јƴ|?C枾 ..憾죾dG{<ĀʻнvZ߽yѤ9QMӽx_c̽IjhSɽgp5V,Ӳ8>@bǾ6!侥(TKҸ3*طxu J#l0޼6˾f=㽷/ľR>Ⱦbmƾq򻳼w(X|+ vFSկyƫց?o*lԅ$Ttþ'&þ AӮ8KwsȌDsptdMb>νh~Z{~~ĎY⨾e,қ%cg84;%էO3ʗC5^=B%#_FWū8ox>>C%/Gsټ,=:o",CA6C<3$ϣXz=>UyS=DV̽ =` R k< Y8 >IL #>?Wx(w>CN[`9>s;J{vxN>JZu-2>S[=@!>PK.?>=Tub<J>Ob[0>0T?Nj*7>\ͅ`4'|>-t=^>= *V>`<=R>W˺>N=h>+>\=&N>b?='Ag>x=D,>-D=a)jK>-(-8=@=Ӳ>a>,WX= >A >9J>0x=aV) >=J}>~=}y*ǖ>!{=Pg>с=0>Ԉ=<>=4!P>m=w8B>`!=8><><v><|>O<M>dr<{>8; -ju>`m [>js>?Y>W >gv;>>$5y 4*>D>鑨>0d>,~>urLLn /<> ˼JS>%mMa>iѼ$|>Ob>t3s[>IS;6 .>s />L>kH>pAl`>O>?;6\~>Z<y>8-ub> H;jԽ>ER6 >OӼD>6D:>T;\A>9<\;P>=P#=[$F><}1-`>ek3=%Wg>{=0l%>͢i=N7k>,M=0_:>f(=va6?_=L>/L)P.? Տ >AM>_p?Ib+7 ?a.ļؽ>c]|>٦]ٽq>.g>Zѽ>nHҽT >MP5ѽ}F>R-M>a+L>m >g*w>ޡs2-f>y(asf >gf> ޽b >sLf>`н\>u[;8>ǽPܼ>!qv?;cM5?{?tGk& ?7s;?w4ݰ<4?嬽7=1?ĆIQ=̚?tĽ+3=m?NH^=4 > ɽ}<?ҽ@t?޽w= >0=L>/ %=>E`н>>dq Q3:>D=)d8>C=DP\`>!=ljE>y=ll׹>$=qp\>>=~9{܈>*=_+>=YQW>Kc=[,|>wu=P)+>P= 9>r=6T>)k=B* >Mf=<%'>>%i>=o >= {q>'=aǽ>~=۽[>=M<>MJ="⽭ >=q/>4=>e=ٔJ> -="V>@0=/<>V=|]/J>=>#^=pX0>=>Dv={2?>/#L=6>P(= >='ί>؛=ڽݵ>=^>H(=+%ᱢ>+=Mý 8>?=Uu>ev=M>=l>Lֺ=#d$>͂i=Gal>~=LȽ3ʎ> U=ν.><ٽ|aB>a0=ɽj>A=_ Q> =T>,=Wîd> =҇?.>5l=RLֆ>=Qvp>=6t>C=H>!6v=,ڼD>_9=>A=[;3{>='?>'=?<>O=`.Z=;\>Jn=>j=i$s}> k=`ܻ>\=m<>9a= D<>R=P)Ikg>0=EhQ=;>\M=2Q=s>]=GY=! >\E==d>FL=O=>hV=a^=Q>l==)>k*=<*}>_ k==<> K=t<> o=>= m>۪=e!g>P=tD=4$>{5==/>=7=>5= =o>/CT===O>UO=fvR>=C猽P>a=J>=>=b>=aYj>=o> $=R>=mPP>=>y>>/p>=$[>,=9>T='(> ><>׹u=0+>)+=ɲt?=3?>=2>-=7P<[>J=A=>h}J<(W=>>09;ی=>6P&x?1_=g|^?k3=]<?;$=q=S>f4=E6[;$?j =M ?]=Uw ?䬡=d="0?\^02W='?twk{?=̲?"Uz=E>Y>m>>X=>Gmt=g> D=?|qU,C=[ ?'=z?(=ݻ>?=̓$&W[>$ i= 73 b>i>=cL!Go>X=vT>v=N=?z>a=uU#w=">=m>go=J=|>C==o>X=d=5/>'B=膩=>Q==>H=>>E8=f>ϯ>`=yU=~>Z^=>>>N'=G|+>c>}U=W >z> =:&>+>B=$+>y>T=*>%;>E?>BK<0R>F>=<7d>>>k<@>aƨ>< m]>F>6d>J<'p> >.;%e>j>"v>G{z>h븃>\>3G@+>><{>>F-;>dڬ>ﳲb>4b>6>5>:;< J>><9 A>|><.>NW>rn=>F>5=Ya>~>@5=1=>Ů/=2>~><=t><=V>B𴺸>W> *>p)>E>=ѷ >WK> =>Xȼ~r=r>h8!xl>>iXI>>VB+>>vc|C>>}+:#>΋>՞-;>>ý#>|>>>>>`ٽ3>> >>w"O>}R>BsE9>%I#p?B>3 C> TͽL1c>ˣؽ{^g>t} >wM+D>Ķ 6>1yN>!%U >}F>>\B>I˽{Žp>N `mw>/ >=ls/(1>sEP >Bq0ƏkS>^i,>*X>ɹ'BM>Žϔ>ҽ>rn^'>>+>3ҽL>%ރSj>G ^>Eoüe>F0?)5>&8::>?@">2aG>} "K-X{>?x t<.S>yڏ8(=>$v|=u>Wp=O>7/ 5>y8Q2}ޛ>RgYK<d>m= >QWd=N>_=I>l31=D>lW|=Is>;Ј= G>xֹ|=#<>! k=R>cX;=>QpV]>B+L}к><=;`<Iop]K=G>aL=>Nm}<>*O ּD>%54W>%E_N>!BNL3> d?=B>N=>`!Y><>;pq=4>H;:v=O>N =>ۄ@=Y>>K=P>=?>=#>;>+`>,7>c>>YB3= V>l[=gS>~jF=Z>aS=K>,P=T>L:>>LKo=:>~^5=4>CU%j= >_q=g>j=^># 7=#$>\+B=>cG4u=>pG B=J9>u?sj۾PH*<<6a徣̾r/t˾S5yi qL ʽ>CtYlw!_i 彖`Kޢ?ј."-BM:(#g ~z`W%ݾyĦ%1@־J̽)yjoJlmeݾн_xd>ľXUaMkž=6u_Ty>Ѿ͔t`ľeѾ#<;þ?ݾ=φw(9<缾vX$=3q+z޾Uq=ſjH辉=79PZ=dOJL=rGԾ'l=d0Ͼ&=) =^`Zþ}={b?þĖ=dxľͅ=]= f;a=GmdT=iw=y%=}>rBK >NpηzX$>7$ >RmBv>w,a$<>BHֺ@>N:(>iX==NNYt=F=g0x+,> [a ˾d1><-[ľ[>.CiB>wҾO>2 w~Lsn>.%Yňw>1  վ^X>o"BȾk>m%]X>N3cb>76־ք>5HJ޾{ԁ>5=(>Ͼ;>WGQɾN3>]p̾2>DWWfվ?>ihѾ>ؾz>0qCݾ9v>i`l$o>scbX-ھR T>hKIAiJ>=iվ0>$ҾB">:r'⾁&>C03辩Y9>o8쾦5J>N)vhD>Db.,>.I)oC>d_p31>,p7 Y>\g,b>M:!トZ>M ;}d>x/ؾ[>йjٸ۾>$]/޾>6mM>*lu>U6V#m>9i $&>ٹc T%>%^ǒֵ'>ᔙ8n .>Ɗz<>Ir N9>}aT7>B\E>'5!A>o; !XG>V!Y>COʽ !>Z>+wd>d`QO c> Z 0Y>uCXe>L&P\>aS7Cb>q]>ҽ$@Z>Ƚ \>8 K>cCP>)S>Fo2 S>>uN=۟#>u,Vw=+R->pN=*=>&Vc=_J>tM0= [>U'=pf>K=v>LU>=@t>Ku9x=3>Ttf=l>K(=z}>T=qS>'KuTx<)3>bL1=)D> UM1=b*#>vU9=*R>taNfv=~n>U)Dž=X=BD=8 >хE=V=?=={>B=*>0J:=z%>z==j>4D=4>:8=H>^0t=8>5N=>/=0>K5bo=>u2r=9!>- 8_Pe==9 V=͒>=jR\=zs=@5Y=As>+B`y==lCEV==y="/=[=ǰ:=T=6f>=d0M=r=mu,=(w=͙+m==@z.-=5=:5G=1=k;2=ٍ=x>=W>GHRo=]>"GB=.6>9D=L>bF@"ӱ=cU>>@|=P>y?S=V2=>wAn =H"> HDe#=P>UcGzI=q/ >T'Iz=7=,75>#Z=L@5>T=D0 >=E,10 >k=Si(9==V'$=Y=|,)=e-=&0=J<5zh=ݮL-=\>hC',(><$#>R1[499) >F!"?=,ց(JK=-Ѝ-q>_.EH>?]_ȋ)0>)5a%H>>?V)"U=> :-<>&Up0> 1{#>(>󹂽)*2)>"G[v$ >RĽy !#>T߽$<&Y7>wֽ&9F>Ͻ_#N>qoĽ DYQ>6RU>݌B>+.>\ ?>k#0R>T36" (`>\!-]>j]>9gW>y޽MY>-Y>2G'`=i>*-y`f2Z>o"GmC>Q|%ݐ:k>@#m < i>8"Ui>0+H`F>p3>/`>?/(`>k⽇[%>8 x0>3kٵP5rcq q3n * 479rQs wsu nNUKng!Nm!fpw Eq%/ zFbs;8TZF!e0@ /o?F 99>㽾=2 ?K- /wߴ-h0m3x{02l*fD.8'{LG4L0x5醽NZ8l޽y73z_6S4:Ns ;8\2i"O;h; Ѯ;m5꼁ND#:eQTLQ/$ (,.8IqBb*fY"19R<|',ǽeI3%}Ñ#P{)T%Y&㮽32*znio!$&{(?Qwz~OMA-}75/17jh2|wЩ5Rˈ#k3O:^FW.>D<~\a+9:$o-&m/ah#z L~vh lNkh=L-U # oU cռY+hZ}vHR9inIo xX бCchMɼ?BpyǾ9"z`(57H5?}9t恽kGWx q=)OXsBu;>jf?%>K?כ>٦?؟r>?R->?!>>>v>yþت>?>H?n5ʓ>ֳ?`>I?ZbЂ> X? ٥ABt>M ?F>4 ?)i u>z`?6ԭ>(h?_>| ?!uƳ>+?p>:>>0>~>>m>þ>ʷ>yW>>І> >>w>!>c>̶>p>>>깾z>){>*>L>$HM4> >*8>>B>/^>k->>zF>-->D->N>F> >N> E>z!>>>fx>m>{5>U>9>R>?>6^?>߭>ϴv>#>><>>>>Ъ0> s?cC>>p嫾i>>Z24>V>SG>˲>'[Jjr>Y>F>&?*7w>>]>B;>嫾 g>?ߓPP>y>D4a>?FM>>d#cl>>̬]>)>TX>)>\b O>N>R5>>8:>>M=>>r:>+> _=>:>|(IH>L@>%H>ȼ>(>O>>pR>;>79^>>ëHL>U>ful+>@>I$"7>>}4>>)Y&>Y>RK>ܲ>> >&ٟ#>>Cq(<>> ^u,>>,_>'Z>ҖC>*>'R=->Z`>>,=N>^7 >>*=>z>>ꍄޅ>EC>{>{>Տ/>0~>OC>߾>e0a> ?5 xz>`2?㝾>ȿ ?#>g>~r׌=ͩ>|m=b>>͉=|>'ܕ>C>=?p>], >>v>΁>T>>: ( >Ev>wYJn=eo>w>8J{>w>)>^->~dT<>`>牾&:>·>D>>jT>>ݞ9e>y>+t>~>즾>~>ר7>d>G ><?4ÁH>uH>9}DU>x>1}`>љ>1u>P>W?AF>Q>A|)>ƛ>{VG;>a>zH}PG>u >txG>肵>xZ>>\Qk>HH>:N|>>">~>9zzI>(>E8>>2 q>o>[>ԈY>ɗ>A1>̃?N>?r|t>Y>-R>?&d>>KMv>?畾p>]?~|c>r>2̑[`]>L>]搾:O>>cJ>>[8>]>qu%>7>HD(>>>/z+>u>ٝ>'>޻w:>>^ʄ*>>Rt0aS>>.сK>5>edJ>>!>|g}>ޒ>c4H>"9{>Ǵ>N$z>l@>ɀ>?>ƌ>r>1U>៾ǒ>.>A,>i>YA>d>C6>>=>wݫ>K>5>/Lɛ>>Q6>>> >88> Q>3>7 k>Fr>}f>g@!>>xJM->0>)Ll9>>P@>g>VID>֢>\Q5*;>-> Ut0>u>9OZ%>x>'e4>}:>eBgt9>!ɛ>Dh<>Ӻ>Wh`5>G>(:a*>?>"Qs >>fw*>4>Ha>>>\CX >+>e=˚>['h(>.>@tfˑ ?O >Q?7>dM ?OO3>~;?9H>g\ ? >JK ?>@ ?s=O(& ?H1>qk%?#>JH;,>>W0$?>IY>M >3a>>%v_w> >>>fҫ>uE> +9>Ih>p>9>Kds>%N>FtE=K>>BG>>9@>V>,O>̠=]e>, >>:=So>=,{x> >,~@X> g>Hg>>"0> >j\n>>Ԁ>l=$´? >{k>>-<>]6=.?='?=d{?|=‹>>f>e=>NY=r >32=vPN>ʠ=^a?ŧ=Q:>h=qayF>d$;>:T\5q>HP 3>V>?0x>Ǒk{>|e>>Q 0><W>'/h5>g9P>e> V ̘><c~> H>ozݾ>%ξ>LTɾɣ> ۾>? Iũ>b9ؾO>Vzt׾>Wwgݟ>O>.F0;þ> ->|u>V\ξm >}kW>uH7>܉9>+!>:{)׾R>c > ]>]' >-]>/O>!K>| {<>! >5>]u֝>޵⾯>\|ؾ>~CoԾ>r4~>)S >3 œ>@_ِb>i|1 ܜ>>^ % 17> qHV>rĸ2+>>Oe>uzK˾>F:y> n>s$+j>&0C[>sʾo~>hS>U tH> F]> 4SJ~>.= a>> Po># GV>^OA> C V> (d>e o)> #=6>/n>?U&Wk>6>޽?>o7 dj> dҔ>NN@B#>R,%>h>Ne)> JL&S>8 *ʕt>0D(Q>,/ )%>N0?>g ü->k 4#,>W ̬4R>N J7̤>% f3x>mV4.^>~Ҷ0zL> >88/>r7ex>:!_> e:NӬ>GO>勣>x:>S@> L E>84a>Y+W> &B >A/h>ݝ#>cC}Ȳ>}%;5&>1o'3c>ϭ`.Ń>ѡ,w.<>U/ I>miI%'8>پ"aH>tՃ>Σ>-۾V>ѾM!JE>O/վ.w>"8E$>@dྦ?>_{A^_>`v?I[>CY>-WX0>aiKs>L߾7T >оK>bʾ(C>cHȼ>^ Ls>S>L->!;Y >\>Li> $Y4>QVML>Y>.L]>P~Yװ>XtNuz>!KDDd>@>WA/E>` 9Gq\>}>6>W'Yĕ>\N$>e7Y>>%J>n GQ>t>C)y>A&AoG>'3XŬ>O}>7.We>mIդy>Eьb>0nGÙj> S:LHT>U @GJE>[zSPM>KE:B"g>JbDtQ>@WC5$>uGӳ&>_)(B]4>  9f>q? > <]$> e;LG>% ,:8>$w?(T>EH>^r>8 j-7nD>86W!>7>8h.Z9>` hYb>7x j> ײQw>.9> !Ã>A7b^>y>A5f>>UNV Ҏ>Є]L>4߾> 9Sž>{3Kf>% D>}"2M>/+@> 3K4>Ρ<%>xpIA7> ~I?>OR@> cB,G>0vW>:ߑK>Y &/wc>S iMb>T8O7C> kW>CT.`>o:^f>/gDhe>>EH~>iQ9>}FV>>>x>_fzK}1j>v>@T6'>{:3X>ui#}>nf/2 }>-]㾽G'{>vھ3->+־V) >ݾ sZ>!꾦^>_,]>8~>߾IoD>_H>F{=q>Rw>:W>E@X˗>޾R>ŵ޾V{>վvyPH>Ծ`,H4>/ξEG>̾:=͋>%־>=>0Ͼ?g:>1;Q+7>ھ;ɧ>};D)o>-Ͼ# ˄>fվ Z>L꾭YH>y=h"?Lj>=|"?߄>J=#? r>~=#?>G=$?!M>w=#?q>=*!?'>=ī!?i>Fr=^,?ՙ>Ӛ=%?#ڛ>=O"?7&>=I$?> >B%?!>a1>dn'?.>D >C$?j>="?>=!?J>G= ?t>"X= D?[k>v= ?v>>=x?%Bj>=?IR>.=P?_[>o=?Q^>SM=;'?@>f=*?;>8=+?5>s=i*?\[.>(=e)?2>^=̒.??4>=,?O7>= /?6>x=1?d;>P=e(?+7>V=['?JP2>j=7&?0/>1=e'?8>R=`&?K2>wX=`#?"i7><=?"#?x6> =$?f@>=?BB> =1 ?K>N=x!?)>>4=$?j8>0}=V$? IB>1B=̛$?AI>7=!?dQR>әs=fp ?XN> b=?qF>=d=75?>6=.?/ƚ>`=B4?>̣=r8?y2>P=;?\>A=V&=?(>=[9?:>> =L>?RR~>=E??{>q=$??t>O==?J>bv=[>? >==?p>y]=:?3%j>=-;?]]>W=˂={??jl>=k@?ݘm>=˷=?b>ڎ=d>?h>=;?2^>;=pr>y7?b>Zo >I9?ِl>>v;?l>>|:? s>+ >29?x>: =$=?=;?w>;S=~9?V>1=7?G9M>ֳl=8?iN>^zB=5?AG>p=G:?˯V>g= Q:?W><=';?W>-=G9?oY>R=V9?fS>g=z9?S>I=j6?'O>v=#&5?H>Q=z3?ZA>#&=#!2?%@>>=U6?JZ>>BX4?yU`>n>93?m>7 >G@1?h>+>e5?@-r>'=E6?>JT=U2?e>F7>1?}>L>4?>g> 7?\>=-?!ѕ>(>?.?ݏ>]>6?Vz>>-2?o> >E.?>.>(+?{>=2*?^#>&=*?Re>=S)?1U>O>0?|>v>1+?b>>9X.?T;v>>)?N>^>h'?>Lj>-0'?>a>T(?O>Վ >Q-? q>,>e)?Ӎ{>h<>%?ǃ>V=!?-r>=%?>=;97?_U>f=t#?>(?N>r)?>⒤*?8(> L?W}>PK?Mx>ф'J?>=s=$?w>i=9K&?ٚ>=%$?5i>,;=V&?>=5*?\> = @)?o>`==vZ)?g>`='?L>~=??Gv>r=??o>[-=E=?k>Nj=>=$;?tp>=h;?v> =P==?z=ɐ=r!?E}}=u#?!q>!=$?w>t=y(?סG>n= &? L>=#?S>iU=;!?jH^>τ=0?vO>VT=<1?ps`>F=v0?3Od>^=3.?i>@=d+?p>F6|=51?3T>=h#?T}>=V(?\ww>QK=2?C]>䈩=J,?vG>=l3?Z>f=&&?m}> =C$?>rz=#*?Tk>Z=2?"W> AE         !"#$""!#%!!&#!!%&' (()' ''*%**+%+,&&%+-.,,+-/.--0//00001223442215532*'335*+*55-+10--5143'')4112  67889669::;6<6;;=<><==?>@>??A@B@AACBDBCE87FE7GHFF7GIG77JI76<<J7>KJJ<>LK>ML>>@MNM@@BNONBBPOQOPPRQSNOTULLMTSTMMNSIJKKVIUWVKLUUVKTXWWUTYXTTSYZYSSOZ[ZOOQ[\[QQ]\^\]]_^`abbc`da``edfdeegfhfggihjhiikjljklkmnmkkinZniigZYZggeYXYee`X[nZmn[[\mlm\\^l]o_po]QRpp]QqPBRPqqrRBDqrqDCsDt?==ut;vuu=;:v;wxyyzw{wzz|{}|HHG}{|}}~{{~~GIIIVVVWcWX`ccWXcbb~}~}}GbbbaaadddfffhjhljjoppRrpRuvvttutuA??tsCAsC{w{xxw/.//ssDsrDr,.,&,&r&#&#                          !  "!#!"$!##%$ %%#&!$&&!%'(($% '%%  )'  )*)*+*+,-,.--,/,01//,0-0,20--32.433-.556.567655878859:7789989&&;9$<;;&$=<$$(=>=((?>*@??)*('))?(<=A=BACD==ECFGEE=F=>FH>??@HIH@@JI@*++J@=DBKLAABK;<AAL;9;LLM9N9M:9NNO:PMLLKPQPKKRQSQRRTSDCTTRDBDRRKBCEGGUCTCUUVTSTVVWSSWWQSSXONNYXXYPQYPMPYYNMZ[//1Z\Z102\\10/[]]^_]Z^]][Z`ay`yb_bycb__^c\c^^Z\deaa`dbd`fgdbchhfb][dgefdbWWWVVVUiUUGijkGGljGFlHlFF>HjlHHmjInmmHImnmjmmkjkikGkiiH|zx_y]_x]x`_y`b_opqqrosorptuuqptvuwvtxvwwyxzxyy{z|}zz{|~z}~xzz~vxvvuvuquurqrsrs~~~}osssss|}|||{EE8EEeeeeeeayaaeHyFHFFEFEEegegggggfhfcc\\\223333444.6.6:O:opootpp  wt w       yy                ! !!"""#y##{y $$ yyw $$%&%&&%$%%'('%(%&')')'(*(+))+)*(*)(&(,-+--+*.,-*/-0-//**.-101-234.43567895877:5;5::<;=9>>=9885855?/?/0?@@?@6;654.10/?0@1@4164@246AB>>ACACDC DED  EEEEFEGFGHDEEFHIDHCDIIJAACIJBABJKJKLKJJILMLIIHMNMHHFNONFFGOOGKPKPQPKKLQQLLMNMNNOOOPPPQQ7::67GXXGOXXRSRSTRTUTVUTVVWXVXXUVR RY Y! Z#"!YZZ"!{#ZZ{Z[ZY\[[ZYXW\YUXX\YUYR8[[98\:99[\Wv::\WvvWTS]^]__$^T]^^T^$#^$7`aa:779bb`7cddbcd`b=cbb9=>ecc=>fe>>BffBfefececX:a<yHzhcghijihklmimglgmnopqpopqrkrqksrtrsmskqlkjhuvwxpvnxnvyvrprvwvz{zvy{vz{|}|{yrtyt~~t~tstsmsmimijijujuwzwz|z||}|}                        !" !#! $%$$&$"&%%'%'&'%%$&&(')(&&")()'('**  +,--,-...///0+1-1+1-2.2-2.3/3.))/3/)4)!"!)4!565#!#56#7879#9725:6:5:6;7;6;7<8<7;0:1:0:1252434243)==> =>?@AAB?C?BBDCBEDFEBBAFGHIIJGKLJJIKALKKFAKMFNMKKONIPOOKIHPIQMNFMQQEFQRDDEQSTDDRSUDTTVUWVTTSW@XLLA@YJLLXYZGJJYZ[\]]W[S[W^CDDU^_^UU`_ab``VacdeefcghffegihggjiZijjGZkleedkmgeelmjgmmnjGjnnHGocffpohqppfhrqhhirstllksumlltuvnmmuvHnvwPHHvwOPwwxONOxyQNNzyRQyySRx{z|{xxw|}|wwv}~}vvu~~uutttsq~q}~qq}|}}z{{{||riZZrYZYYYXX@@?@?rropppqrqSyyyzS[S[\[\\]\]]bab`UVaWWW]]aVWNxzbbb__`b^_C^^?CC___zz\                         !  !"##$!%&$$'%$&  !$ &(&&%()(%%*)'+**%'#+''$#"!!,-,,.-/-..0/1/00213122435644257522078700.898..,9(9,,(:9(():89::;878;;<7=577<=65==>6?-??--@-/@?A@A1A@@/1BA113BAABCD##"CC""E+##FEGHIIDGCGDGCJGJKHGGJKLKJMKLLNMOPNNLOHQRRIHKMQQHKQSTTRQUVTTSUWUSSXWMXSSQMYXMMNYYNPPZY[\ZZ][P^]]ZPO_^^POYZ\\`YXY``WXabccdaeaddfegeffhgihfjhiikjljkmnllkmmkiimoionmnpnppqpqrqrsrststutuvu vwv   wxw  xyxzy{|yp}n~lnn}~~}}p}pp~~ljllhjjghhegeaeapqrqrrrssttutuuuvvvwwwxxyy||||{zBBB333444666>{yzyzz{{                 dccdooo !  "!#!""$#%&##$%&%%ififoi!Jfd__OOO'OL'''&'#&#'LLJ#!#J(^__)()***+):)++,:+-,./0*/..+**+*E/**+E12EEF131FF43F545F##D52600/2E2/7622178711389833:94;::34<;445<I<55DIR=<<IRT>==RT?@A>@??=>:?A;?:?;<<=?9:B:ABCDBBECFCEEGFHFGGIHJKIIGJJGEAJEBAE@JALJ@KJLLMKVNMMOVTVOO>TOML@>OOL@<PQ;P<,P;;:,P,--RPQPR=<QQS=>=SST>>T6U0VU667VWV778WXW889XDX99BDVYUZYVVWZ[ZWWX[\[XXD\]\DDC]^]CCF^H^F\]^[\_`Z[[_`YZ`)_a)cbaacc)aadcaedfeaagfbhggabgifhigjkllmjcjmm(c)c(dncjcnnkj[ml(m[[](^(]opiqpoorqsqrrtsuoiihurouuvrhbvvuhcbvvbctrvvbttbbawstxywwzxzwttz{z{|{|}|}~}~~~~~x{xxz{|{||}}}~xyxUWUVUVNVNW`W``\\\[ll[lllklknk                   !   "      #"#   $#$ % &$& '% %('(()()*)*+*+,+,-,../0/./0121303130./44545676558797885:4:5:41/1466;6<;767;<=><>;?;>;???@?A@BCBD>DB?>@B@>@AA!EAEEAFGFACGBABGD>H=H>9=7<7==9H9IH8JI98IKJ8:K8K:L1L:ML12M13N2OM2NO2PMOOQPRQOOSRTRSSUTVTUUWVXVWWYXZXYY[Z\Z[[]\VX^^TVXZ^_^ZZ\_`_\\a`RT^_QRbQ__`bcQbPQccdPePdfPeMPffLMgKLLhgLfhihfghiijgkgjjlkIJkmIkHImmnHonmpnolommklqnprqpqrstsrstGuGtFuvwvusGCsCqDqCqDnDHnxolyoxzoypoz{rp|r{z{p}|{~|}}~r|t|~tEFvFvEEE! !!hghgoooqlqgll " "##noKgkkJKuhuuwwv&$&$#$nxnxxxw-,-,+,+*+*)*)()('('%'%&%&&w                               ! "#" "#$%#%"#&%&#"&!'!&!'()('*)+,+)',)-../0/--/1/202/20343054-0-45-.66.6..6575676878899:5;8;757;45<:<54<=4=3>3=>=?@?=A@<A<B:B<B:C;C:C;989;D >3> 3 212   21  1/ EE FFEGEFEGG$$H$II$IIIJKJKKLKKM&N%N&N%H$H%OHJIJHPJLKLJQLGGLRSTUVRRTUWUTTXWYWXXYSYYZZZ[\(\] ]\](]! !]  ]()\^\)\^_\_[`[_[ZYZa[aZa[b`b[WYaWab`cbdbcbdebeUeVUfgVefVghRRVgi+M,M+M,&'&,iMjNjMjNOHONkOPJPOlPQLQPmklPlk^)n*n)^n_o_n_o`c`odcpqprorcpcrsrnonrtuvwvuwux+x*u*x*unsnuystutsrsqyqsqyzqz{|{z{|}~}|~yyxi+ixijijOjOkOwwwvFGFQQFQlQBAB@A@@???DDD D>>?<@=  WbUXSTSXBBCBC9C9  Dmkk,,,hhfhhgffdedf{pqp{dd}{}d}fff}}{{~}~yz|z~~z~mlmmvvttywxw***NMNMMKMKK}}H^H}^\^}}_\{_}`_{`{zzY`yYz                          wwuwu~IHIKI  yUYUy0U.0+.+-+R--00yxxlxTTSRQRRx !"#$d%&&ediie#"%'(##%'b)''cba*+a`a++,`,+--.,+-/),,./$00T123202011414-4-\]**a\567895578:;665:<:55=<>=55?>9?5@ABCDE7FGG87HF7H7I6JK;J6:LJJ;:MNJJLMLMOOPLQROOPQ<=QQP<:<PPL:STRRQSUVQWVUUXWYWXXZY[YZZ\[][\^]\_]^`_^`^aba^cbd^dbabecebecfgfdcdfgdhdih\Zid\i\d^>hi>iZZj>XUjjZX=jU=>j>?hQ=Ukelmlelmnonmpqorpomrofremerrfpgpfpghhsptpss?tuvqqwuptwwqpqvoxovoxnynxlnz{zn|klz|l}|zz~}{~z{~ekakaa``_`_]_][][YYWWSWTSSSY[[||}|}{nd{dd}~}~      wwt?t??9wuw?shk|k989888GDGEDGFFCFFFFHIHI76KI6IKKIKJKJJNNNNMNN           B@B @                                       _                         (                      (   (  "       $_   $"$   ))(   (         ! " ! # " # # $  $ % 4% & & $   $ $   %  $ $ & % % & & ( % 4)) % ' ' ( ) MMO( ) M-) --) ) ) ( * * * + + + , + , - , + + * - - * * . , . / . , , - / / - - 0 / 0 1 0 1 S1 2 1 SS3 2 4 2 3 3 5 4 6 5 3 ' 5 6 6 7 ' 5 ' ' 4 5 4 . 8 0 8 . . / 0 2 8 0 0 1 2 8 2 2 4 POMMLP9 ' ' 7 9 : 9 7 7 ; : 6 < ; ; 7 6 = < 6 6 3 = > = 3 SR> > 3 S> Rhh? > = > ? ? @ = < = @ @ A < ; < A B : ; ; A B C B A A @ C D C @ @ E D ? F E E @ ? F ? hG F F hG G H G F G H H E F I D E H D E H H H I J I J K L J J K K M I J J L M D I M M C D N B C C O N C M O P O M M L P K P L P K K N O P Q Q P R N P P Q R S R Q Q S N : B R : N 9 : T S T : : R S 9 T T U T V U V W X V V U W V X X  Y  X Z [ Y Y X Z W Z X W \ Z W Z \ U \ \ W U U S U T S [ Z [ ] [ ] ^ [ ] ] _ ^ Y [ ^ ^  Y  Y  _   ^ _   ^ ` _ ] ] ` _ ` ` a b a ` ` b b b c d b b c c e c c f d c c e f g d f ! " g f h ! ! g f i h f f e i i e i a b d d g a a g g # g " # i j i h j ! h j h i ( ( k Rk ( ( ORl k RRTl l Tm l l m m .k l k m n 33.m N3n n o NONo o SOp USSo p q p o o n q q n n m r q q Wp q q r WUp Wr Wr s WYWs t u YYs t t s v s w v x w w s w s y Yu [Yy z ][[y z *]z { | **z { u { z z y u } { u u t } ~ } t t v ~  ~ v ~     w x t s w v w t    x  x x       | *| | { |           ~ ~ } ~ ~       j  j j j        w   v w { } { !&%%"!&!! e& Q0$##(020((/2)/((').322/.-433.-13341`,))b`c'%%dc ljife^^                          > >         n  nnd de d      e effi iip pq ps qs w s  #  9 9 9# _R^jij  !!"""$$SQ~utuuFG  2  {{*                   +,+                   0+0       89  9                y                                                          8 < <      !  !  !  "  "  "  #  #   #  # " #    # "  ! !  !    < < < ;0;   # y w s $ % & $ $ % ' % ' % ' & ( ' ( ( ) ' & ' ) ) * & + , * * ) + - + ) ) ( - - ( ( . / . 0 . 0 1 2 . . 0 1 3 4 2 2 1 3 5 4 3 5 6 4 7 2 4 6 8 7 7 4 6 , + 9 : + - - ; : ; ; - 7 / . . 2 7 ; / 7 7 : ; / ; ; < 9 : : 7 < 8 < 7 = > ? @ ? > ? @ A B A @ A B C D C B C D E F E D E F G H G F G H I J I H I J K L K J K L M N M L M N O P O N O P = > = P Q R S T S R S T U V U T U V W X W V W X Y Z Y X Y Z [ \ [ Z [ \ ] ^ ] \ ] ^ _ _ ` a b a ` a b c d c b c d Q R Q d e Q f S f Q f S g U g S g U h W h U h W i Y i W i Y j [ j Y j [ k ] k [ k ] l l _ m a m _ m a n c n a n c e Q e c o = p ? p = p ? q A q ? q A r C r A r C s E s C s E t G t E t G u I u G u I v K v I v K w M w K w M x O x M x O o = o O R o T p T o T p V q V p V q X r X q X r Z s Z r Z s \ t \ s \ t ^ u ^ t ^ u ` v ` u ` v b w b v b w d x d w d x R o R x y e z f z e z f { g { f { g | h | g | h } i } h } i ~ j ~ i ~ j  k  j  k l k n m n y e y n z y { z { { | { | } | } ~ } ~  ~   < < 9 , , , * * * & & & 6 6 8 6 6 < 8 6 5 5 5 5 3 1 1 0 1 0 0  0    $  $            5 3 5 & $ ^ ` _ ] _ l y l m m & $ $ : 9 + 9 <                                                                                                                    ! " "               #     # $ #   " $ % $ " " ! % & % ! ! ' & ! ( ' ( ! ( & ' ) & * & ) & * & % & & + % , $ % % + , # $ , , - #  # - -  . / - - , . 0 . , , + 0 /  - / 0 + + 1 0 1 0 . 0 1 1 2 . 3 2 2 1 4 3 4 4  4 5 4 5  5  / . 2 2 6 /  / 6 6   5    6 5 3 5 6 6 2 3 4 5 3 * 7 * * ) 7 7 ) )  7 7 = >==7 ' ( '  ( (       "          8 9 : : ; 8 < 8 ; ; = < > < = = ? > @ > ? ? A @ B @ A A C B D B C C E D F D E E G F H F G G I H 9 H I I : 9 J K 9 9 8 J L J 8 8 < L M L < < > M N M > > @ N O N @ @ B O P O B B D P Q P D D F Q R Q F F H R K R H H 9 K S T K K J S U S J J L U V U L L M V W V M M N W X W N N O X Y X O O P Y Z Y P P Q Z [ Z Q Q R [ T [ R R K T \ ] T T S \ ^ \ S S U ^ _ ^ U U V _ ` _ V V W ` a ` W W X a b a X X Y b ] c [ [ T ] b Y Z Z d b Z e d c e Z Z [ c e d e e c e d d b b a b a ` a ` _ ` ^ ^ _ \ ^ \ \ ] \ ] c ] f g h h i f j i h k l j j m k h n m h m j o n h h g o p o g g q p g f q r q f s t r r f s u s f f i u p v o w n o o v w x i j j l x y x l l z y { y z z | { } { | | ~ }  } ~   ~ ~ ~ | | | z z l l k u i i x x x y y y { } { } }    k n w m k m m w w w v w t s t u s r t r t  q r p q p r r            u v v v p v                                        }      }       u   u                                                                             ! !             !    ! ! "   # $ #   % $  " % %   & # & $ %   % % " ! " ! !   & & & # ' # ( ' (  ( $ ' ' # $ ( ' (   $  ) $ $ ) ) ) * + * , ,, , ,3.3,- * 3, 3* . - 33/ . 2/ 30 ;<;0 / ;/ 61 S26/ + * - * 2 + 2 1 2 3 4 1 5 3 2 1 2 3 6 5 + 2 + 5 6 + 7 - 7 + - 8 7 9 8 - - : 9 9 ; < < 8 9 = > 7 5 6 ?  3 ? > =  3 5 ? 3    4 3 =   @  = DBA@ < AA@@ < ; AA DAA; A B C A A D E D A ; F G D D ; 9 H E G C B I C I A C C C DA 4  J 4 1 4 J S1 J SJ ]_]J J _0 K . . K : : - . 0 L K M L <aM <aN M O N aa`O K P 9 9 : K Q P K K L Q <L 0 Q L M M R Q S T R R U S R M U M N U S U N S N vdO `yO dxO yO vN xvO uS vT S uV T uuV Q R T T V Q B V B I B I I . / 0 G F P V H G G P V V P Q D B L L J D J H D D H F B @ L @ > L > P L P N L ; : I ; I G G = ; G E ? ? = G E C ? C A ? skkkddcco " $$"$%%$W X Y Y W Z W Z [ Z \ ] ^ _ \ Y Y X _ ` ] \ \ _ ` a ] ` ` b a ^ ] a b c d d a b c e [ f e c c b f g h i i j g k l j j i k m l k k n m f o n n k f e f k k i e p e i i h p ` o f f b ` _ q o o ` _ r n o o q r n r s s m n t l m m u t v w u u x v y w v v z y { w y | u w t u | | } t w ~ | { ~ w  ~ { ~  m s x x u m l t j } g j j t } ~ } } | ~ } } } g } h g p h p } }   {   r q X q q _ X r r s x x s v x v r z z v X z { { z y z y y { y                                                                      p  p p                           !  " # ! $ " " ! % &    % ' %    '     ( ) * * + ( " ( + + # " # # + ! # #  !   & , - - $ & ( " $ $ - ( ) ( - - . ) / ) . . 0 / 1 2 0 0 . 1 3 2 1 4 5 3 3 6 4  4 6 6   7 5 4 4 8 7 9 7 8 8 : 9 8 ; : < ; 8   < = > ? ? @ = 7 = @ @ 5 7 2 3 5 5 @ 2 0 2 @ @ ? 0 / 0 ? ? A / B / A A C B D E C C A D / B * * ) / * + D F E G F D D > G A ? > > D A E E F H F F G H I H G G J I K I J J 9 K L M K K N L O L N N P O : N K K 9 : P N : : ; P Q P ; ; < Q Q < <       '  , & % % 3 , 1 , 3 4  < < 8 4 = 7 9 9 J = > = J J G >  & & $ 3 % ' ' 6 3 6 '    6 [ e p p  W W Z p p Z [ W  X W             Q Q Q . - 1 1 - , H H I E C B B C  B B  *  *   I I K K M M M P Q O P O L O L L M  Y [ {y^ y~^ ^ ~\ Y \ Y a }^ }a }{^ a d d d d c c [ \ ~R ^_S T U V W S S U V ^W V V l^^R W S W R X Y S S R X _X R Z l[ [ Z lV [ U [ V T [ U S \ T ] T \ ^ _ ] ] \ ^ ` ^ \ \ a ` Y a \ \ S Y b X _c od c e c d d f e b _ooc b e b c g h f f i g f d i j k j l k k m n l l o m p m o o q p o r q r o o l s k l l n s t s n m u t t n m v w m m p v u m w x y u u w x z x w w v z { x z z | { y x { { } y ~ y } }  ~ s ~  t ~ s y ~ t t u y j j k s j  j j s   }  } { } g { | | h g g { g g i i i Z      + IIJ JJ+ I n n nn _ _ [ Z [ ` ^ ` ^ _ ^ ` r r d i q r a ` Y a X b X Y Y p q Y Y G b b    "  #"  # " [ T [ T ] T ] _ ] _ _ [ [nyd C @ @6 7 > 8 < 7 < = 7 = < @  =  E      CBCCBDECQVSVWS6 > ? ? >  G E D H V B A E B E H B F D 9 P F 9 e b e f h v p z v | z h | b p e f h | z v p b e 0A8Material.001  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~                           ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~        !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~@Ao )??u?'??L+?I?V$?} ?!??>i?sK>#?F\??\>#?m?? ?$~?՗?/P?d??3 ?) ?Ps?H?,?/?V??E?>]O?Xq?_? ?em?9b ?P?u=?<2>i?>B?>E>@>9>?F>>>6w>'>T?>zq ?>J?h>?[>#?>?OY?0>>>?i>f?>}?>?+> ??e?>?>B?5?v?U ?@>%?> ?>[( ?>h?|>|>B(?+f>I(?>d+?1&>ƥ*?*W>A)?>?&?z>$?E>}"?)ZY> ?rv>?G>τ?UN[>?G<>Z?>E>h?PR>?mp>z,?!>@>i+?E,>j(?z>,?x1>&%?V>/N$?ʇ@>~!?XW5>?G->?e)>R? >c?(4>?Ӣ>`?5 />??>x?>?&&>?%v->Cr"?!>q ? >s?؁>*?>?V>,?FC= ?s=uZ ?e=2?=E?^>N?N=t?>=!?>?I=?$=K?1=!?u=P?A= o?Y= ?o١=T7?=H?=4 ?'=$ ?e=t?L>?> ?M?>?vL>;?;s>N?ď> ?>!?Y1>o%? >*?.:>)?->aR,?2v> +?=)?\>+?V >y$*?g=)?&=W''?=+'?R=Z#?(,!>{%?>p#?>"?d#==#?\=Y&? \='?Ԟ>o(?M=J?=r"?=q?}!=:?x= 6?=$|?R=$` ?)=x*?bf_=K ? =?O=G?=t?)x=?̴=N?m=?=X?;=?'= "?>9?\r >b"?=u?m9=?2=ж?Yš=? = ?%>)A?(=>v ?a>q?>H ?ף>?|{>W`?&>?GY>Ӄ&?[?='?倅>. ?Ն>?l>?>}A?<>"?>4?>?C> ?nv> ? '|>n ?i>?CT>?>~?>fh?>[[?>3j?><? >kJ?Ug>`V?=>X!?>q?>p"?&>& ?>ٵ?г>?z>c?[x>g?>?'.>zP?0>"?k>&?]>?>?>?w> A?}> ?>+?ß>1#?>n?is>:?v>?I>2?>_?>(?Mh>[?E>H7?>A"?>V?]> ?1j>H ?{s>it ?Lu> ?Ɗj>O?%d>?{X>?P6U>-?>t?>i?U>|?WC>s?>?X>4?%> ?>?>Ǡ?B>T!?0e>I?W_>ys?AX>?3U> ?F> ?SR>?D>?{7>l? 5>?jP>?.t>]?7= ?l=?Q=j?}=J ?pD=| ?[=?-=+? > ?1 >4i?e>E?+M>? >l?[=L ?y\=G ?==? = ?=?>"6?n>?o4>?:>L?I> ?)(> ?u> . ?>?_E&>a?o7>o*?~5> "?c}S> ?~$>Q ?RT>(?iG>i?fJ>-y ?g> ?0Iu>H"?rc>v? >^?s>N?5>]?q>t?mt>@?+|>;?i>_?[>?~>$?Z>z?>?\F>wK?h>?C=>G ?$+> ?>[?B>"?|a>$?>i%? :>u"?#U>'N&?B@>$&(?1b>EH)?n>1(?}z>&?D>Ps"?%<>&?i> $?>! ?>i?P>#?{>?o>r?#i> ?Eo>d ?Z> ?L> ?줖>?P>9?h>?xҢ>#?q>3 ?Y>* ?,>Q ?:i> ?M>ǽ?T7>P?>d?>i?N>2?.>?5>"?>?y>#?Ƿ>?{>dx?>*?V+>t ?*>>??9>ݶ?>!?p#>*?a4>a(?d2>A$?yu>>׿+?/*>5%?L'>nQ*?15>+*?.&>0?DR+>20?->/?'>.?3>z2?TO>,3?)@4> 2?4?*>^0?>Ϥ ?%}>Y ?hy>T ?}>~ ?Jd>hZ?gQ> ?>7?L> 7?Jԓ>4?@>l%4?ꯟ>g4?>8?ˌ>e5?`>Vf6?yv>L8?Xo>l6?R>8?>4?Փ>4?v>v3?}t>|&3?i>2v6?}W>B:?wo>P9?Hh>n9?{0y>5?\]>r9?T>7?G>À5? ~>v2?w0>U1?'>1?e>jm2?t>\2?1S>>3? -[>2?=>#4?=>9?r4>8?Y@>^;?I>ƣ;;?|)>%>?+> >?'>=?y>>?E>>?f1>>?>E=?2>A :?%wX>86?{`>2?Y>4?ݪ> 2?1>0?R>{2/?4>OW/?»>P7,?>_y,?e>i*?-Ҭ>%,?>;*?>7+?EI>,?á>/?>hy2?>1?$*>Y3?K>5?H>85?R>j4?Dl>1?:>6? ^w>ڒ5?j>4?s>7?|g>f06?H>|7?J>"5? ʔ>K=3?~p>p6?~>6?ML> 6?y>~0?$ b>U/?_>b0?b>,?dS>M2?J>y1?|7>1?<@>8?6>;6:?/>c-?u<>#.?N S>&.?PY>k/?C>0?A5>10?W">d;?>>7?1>>.6?)>2?=>.?[> -?t>*?">#)?)>g&?">$,?Q>w.??a>L/?.YE>P2?=>CW2?a~>I0?ǝr>߇3?o>5c5?>-?O@> 1?>?>64?*> 0?6>'?>J'?CU>(?3>c{%?#>%&?>ղ)?+>B:?>29?"޺>"=?>1y;?K=?*>.6?T>a2?S>:?>{=?.>@?_>??2>$??>@?a>"D?E>{"C??5???b??k?͓;? ?O=??:?E?r:? ?R7??mY6?+N?7?V>)#:?>{i:?x>L6?>t2? ?0?N?Um3?>1?1>;?Ͳ>&]=?!>;?ʭ>T9?>4:? #>A?!>ѯA?:$>^@?=}>Q??j>A?ҽ>@?P>V]79?>8?2>?;?>ӽ6?>}$9?H>3??{7?x>*/?kJ>3?RI>-5?4>:#6?ḷ>!w5?3n>r>?i>H=?Su>tB?D>f@?g?(-?@2 ?0A?G?lA? ?LA?p?\:? ?&;??H:??R4?s?Q:?w?u/??[2?m?h>/??1?mY?3??2?q?/,?>?$*?|c ?=?N ? )-?ګ>-? >S(?6>I'?3>p])?>r(???U&?{0?S"!?>c$?>%?>d%? >Q$?h>Bz?>p!?6>+?H>!?f>)?t>#?-'>'?o>h(?a>G/?Y> 2?~>X9,??p>;U*?>?>?5>??X(?w?{-?:@ ?(?!"?#?&?W?.!?+"??!?m97?}>J?-??GH? ??IL?^D?cJ?(?O?\.?^K?7?^hN? ?I?k?ǃI??M?n"?fL?w?P?.?~Q?#?iT?>#I?XG?6 ?pO?Q? )O?)Y ?O\J?P?9~T?> sR?A?)vT?T>U?>=Y?O? ]?-x!?7Z?76+?V?J6?R?Ց??O?D?dM?eG?0K?_N? yG?EgQ?EC?%?X?k2?~U?>?D4R?ٖI?.N?K?K?WzM?lI?W?j=?S?C?U?sdA?Q?RF?O?qI?Q?ÛE?S?E?^V?SN?9>Q?'>ŬS?>T?>1W?a> v[?q>qZ?3>cW?>IU?> [S?摯>Q?f3>}YR?h >T?$>!R?N|>P?>zM?ץ>N?B>Q?G>O?>jP?2>N?n>/M?Yk>tM?x>^M?m>8O?PQ>%M?>>&L?C>K?>J? y>sH?>QF?e4>iWI?۠>&F?!;>D? >GB?>I?a>K?2>2:?َ>;?>A?w>6A?To>@hE?>rI?>w@?v>B?sc> ;?>£9?v5>??=~>4F?p>J?ر>N?!=>eI?I0>$(J?>~K?OW>G?>yG?E*>B?^>:C?p>eA?{>9?>hL?_a>#U?p>uR?4> =?2>R`=?28?XS?iQC?0O?hB?u?N?G?Q?B>@?T?zA?XQ?7J?U?C?lY?9?=)[? 9?W?A?pU?B?X?:?bX?}6?DQ?u9I?@Z?:?XW?8?CE?=?}A?1B?W??F?1$??^H?=?K?g B? =?u>?5A?9?I?2Y?O?:?u?D??8?O ?ED??K?C ?ZM?o ?U?? T??z[?9?[??vZ?m>HT?%>vX?T>\?[?sJL??E?_?8;?τ> C?f>>G?v>=?#>uL?y>LS?>kK?v> S?[>&P?+>zM?uQ?i;6?0>sI9?y>6?@j>,>?mX>(D?J%>_I??>;?Xt>??>(D?>МI?i> 6I? c>D?t>?:A? l>=? >V;?#k>6?巰>LA?Q>~=?U>{:?ji2?x:? v'?9?k)>X">z>>N>>9^>%>>X=>xG>C>>>q#> >V~ >S%Z>h=x>/=UP>d=:Wd>@j=7>=!@v>i=(>+>>R>a>e=_F>F|=V> >Tĩ>}"/>۪>c>><>lM>b>T>F>[>0>nn\>Wc> C=0O>Q=M?>B=>O>M>д>)?Y>uW>g>0>P>>b>v«>Ylc>S#>lg>M,> %s>q:>]>e\;_6>VJO

B<%7><7>5,<>w;Y~>'<j>UyV<%>h8=6>V=tF><8Nz>1^%=9}>=2>e=Ξ>#>U>d>ƒ>q==@m>mǴ=bU>Z=\Q>% =\>'P=b>=]F>=C/>=|G=>LN=;J>ʤ=}> >v2h>k=5&4>`=(eB> =ݖX>=e=V}n>>0>V*>"p4>v=>>,=T>=w> #>`>>>>ɓD>^H=>I0>k>M#>0F>+;>>E>,>]P>o>&_>r>_>U>Ub>R~>Be>,>k>f>l>=H>{>>|>>|s>>>>>>T>>i>1'>>yx>W >}>>>i6>!>>Ow>>>ի>>A>]>/>{>S>{>>A>>V>!> t>:> _o>>z>>>>`>u>>n0>3lt>>˞>4>"> >~ƥ>F>Ʃ>>>8>>">w>IL>>]>?>{>M>>!>e>>{>$>D۱>"5>값>a>F>>]>B>Ԝ>8->u>ԹR>>wB><1>ٴ>>U>>f>d>>K:>[#>?7>>>>>)>>҄>*>'L> >!s> t>FW>T>+/i>D>{>0>>>4K>!>l&>2>up>{>úA>>/> y>94>}>9>>Â>[>7O>>'~>gC>'>V>v> TJ?x >QLF?͐>>?><>;M?>VQ?3>aP?#>%#K?>Q?> L?y>QN?>#O?=)>hS?>t(S?ܸ>R?O>G?>C? >CA?)>Q?۰>?>tF?^>m?j>u)?Ɗ>X&?>\q?T? ?7??x>#L??#?D>?>A?~>{M?*>X?_>?> ?#>?ׄ>P?>R?;T>g?ɲ>!?>`?׬> ?>].?>E.?a> .?>)?v>j&?ԛ>!?>/?!V>W\'4?>E2?>΍-?=}?k'?M1??^c ??v?4?uW ?y7?>PKY:?c>¥7??:D?TW?eA?>@H?7>1A?y\> G?C>gG?> P?D>M?>BwM?>yXD?>kE??=D>H?> E?>K?8K>;C?>PL?>YD?>K?>BP?5>]P?>[R?4g>2P?>2K?>8R?>*U?a>V?t >U?>zS?\>5?>=?Z>y9?W>d;?V>6?S?PC?j?QQ?+?L??]0? ?5?ǹ ?B?Ab ?G?&?ff*??k?h?t?ޮ>r ?ѐ?[?Ǜ?`?yt>t?O?t)?1>O?e>?<??>?>M?->W?:>$% ?W> ?;V ?S ?>?&>6> > >>>!>>[D>82>=>>hz?> G?Gɫ>ʧ?ۥ>~?>F> >g>Ӈ>*>0?>D ?{> ?0*>}$?vp>?h">J>>q>8j>>>w>z>h>q>g>eĭ>L?~>}?>2 ?>>?O>s?G>9?>}>a>T> >t>>>>Z.>y>j>>>.>>>i>>>c>z>%?Y>*?{װ>6?_>y@?TǺ>_~G?鷿>^L?[>*Q??>H?q>E?>` ?Ω>__+?0>ɓ4?ò>=>?Ĵ>.E? S> J?:>2XM?>)^?>$>>~?#>?>i*?>*3?>j;?>A?3>ʊI?nO>>?b۲>hxG?>4B?=ׯ>ѕ4?>7??|>p5?nި>Y,?O>n4?ꮬ>!+?@K>?q>#?ޟ>>'?r>P-?v>!?5>?>?Y>?`v>?>X?> k ? >: ?>?>7o?ڌ>$?_>%?">4>D>s/>+ܒ>U?m>?+>?>+?>?R>ܹ?Ԍ>?WC>;5?a>!?> ?>CV ?>>?0>>>p>I>0>>A> >s?X>O>>%"?YQ>I>>֐>>>H>{?>  ?>]?>>b>>>>`>C>-%>>>C>6">)$>>y>>BB>a>g~> +>\?>>%>[?>?>>w?hy>A?ȇ> ?6>8?>2>L>>I>8?>?>6?>:?g>??r>/?轑>= ?D>(?0ԙ>0?G>?ؼ>#> ?˾>?>q?4>W%?2>6?g>?6>~n?#ً>?j>1?Ӈ>?>̚?_(>G?g>h? ?YR?Q2 ?!u??_??0?jN ?XY???q ? ?{?Sw ?F?t}? ?t` ?? ? ? ? ?z ??l ??e ?R?g ?L?b-?= ? ? ?Lq ?M? ?/??[?&???;4T?w>F[Y?od>X?w>ܽ\?x>Y[?P>Y??u_??R? ?, X?+?XO??J?V ?9G? ?!]? ?#X? ?T?x?qQ?N'?qVL??ӠH??PE?? /E?G?`I?m;?H?Ω?K?~?>uL?E?P??gV?4 ? U?X?8R?8?cW?]?U?? (\?b#?]Z?\? P??oX?C?P?u?W?\?U?*s?@L??Z?L8>!u[?>T]?ܺ>#X?>V?>ha?/ ?]?U?ԛY??V??a?ђ?N^??Z?1?5Y? "?ҍX?cD"?Z?r%?#M\? ?r_?}?7?H ?jj?o??n?? ?5^?!? ?????Ҫ? ?,??ڑ?J??"&? ?j!?j/?$c?iQ? .>[{?"P>@ ?> ?l>R?E*>8#?>!? f??l>A?>v?X>?>?_>A? >^,?Y>?>c?ح>!@?6Ȥ>g?x)> ?s>,?҈> ?Q> ?>@K ?>կ ? > ?> ?G>/M ?B>?M>.?!>_?B>?>?>V) ?Ý>7?2> ?D>? >?b>;?A>P? F>Gq?>;s ?> ?c>q?1>ʩ?>|G?!>m?(>?~>=I?Q>?I>\?+>?/>Gw?`v>j?>Զ ?د>m?h>?Z֝>\?Ҥ>;?>u ?->, ?>ۅ ?/o>wN ? 1>?">Nc?N>C?B>g?YQ>p?ӣ>P? *>k?}>6?l!x>?kx> ?|>! ?y>U?r>?Ro>?hu>?S> ?|>_?*r>?6 >?~>?(>P?u>?wq>l?Ig>?Ct>?9 >nk?ʨ>?_D{>)?>4?˅>?s>?V؄>O!?c>#?l>$?>)?m>#?m>S"?J> "?ӑ>3?>"?>?>e?v>?T>RG?)Z>?>?n>i?>[>3 ?@>H#?G?o?R?~?WJ?n?A??o>?j.?:?@?-R?%?Y?3?l\W?M?gJ???eR? ?U4?$?5??`-?4?H?`?C?? O?? R?l?=G@?<?7?.?0?Z?c/??O7?a?&:??8D?QM??O?~8?2?&s?8,?Y5 ?#/??(? $?,?k$?$?%?`!?^#?;?f ??.?Ӆ?t)?$B#?+?"?T'?P ?0#?G?#?:?l?'P? ?H?,-??2?1?OZ4?,?p:?#?n9?L$? 2??Vc9?S?D1?&?Hp?$_%??Q3?C?k?\V?.?3?@#? ?b*?!?*?%?P0?5)?1?,H?$?K !? *?R?>*?g%?hA,?H1,?1?0?+?7?1E5?~6?bL.? =?'7?;?%?^>?N?Z>?k?z>?'?P9?|*?>?D ?q3?j ?_-? ?#(??n"???n?ک??h?Z3??9?!?:?S ?C?7 ?D?R?];?Ŏ ?f:??9?}?X81??<:?y<?pC??>"B??YD?;?:<4? '$?/:?ܠ"??? ?c=?m'?4?+*?D:?*.?j/>?3?v@?{-?J~H?+?l H?H&?b@?9?]J?66?1I?0?@I?x?L??;pN?i?jlO?Af?@O?7 ?M??B?֩?qJ??S?Ed?uO?> M?_>zG?e?tK?>xG?{K?7K?I0>D??K?k>9I?6x>O?>5N?M>Q?>$P? D>@kN?Ĕ>P?>8R?>R?q>CXQ?p>ƉS?[^>W?>Z?S>vV??aX? ?W??|\??]?Cp?W?}v? ]?J?2U? ?KQ?"?q\??{W?N?lN?%? R?%?\M?j+?}P?"+?ZQ?2?YO?9:?T?<9?{fa? ?Ƌa??l_??QZ?F!?x%U?:'?X?(?oS?eQ,?1V?F/?I? ;?XQ???F?l@@?q=?k??2?B?Ɔ:?t{E?!@?HC?]C?aE?QJ?D?&V2? SG?Rd?OF?ܼ%?HE?*?7H?&&?A??:B?&?۾C?WV?=?%V?͓;?e?2C?;?.SG?J?_K?X?9???Z?:?z?o??|"?1?'? ?y,?H?1?>z7?{>=?>̙A?+>,F?>k'J?">*F?Ɵ>C?F>H??;>.J?d>lJ?>>&F?ɬ>(E?ѽ>B?DO>E?>;?>6?j>0?~>+?L>^-'?N>#"?%>%? > *?0>_)/?~>hX4?>9?ٕ>/>?>??B>A?/Q>A?p}>p@?+>B?N>l>?>M:?z>2=?T>|`7?Yn>:2?ظ>u8?>6?>~1?sK>-?>,?y@>;Q*?X>](?,I> &?w>?%?> #?>A!?(>?k>o?>>?~>=b?>?>ߥ ?+>]#?Ȳ>#?Һ>(?>e)?>$?Ps>|(?><.?\>7?_%>?$>m?>->?>vo ?U>?>?>?>?>?~>r?y>E"?6>[&?}>R?>Z?ے>j?}>?> ?;>5!?'>?>"?>i7?>D?>?t>?>) ?'>˽ ?l>?L>h ?`>?S>&?j/>?G>> ?">)?8> ?> ?ǃ>'?>a?>?J >?`>-? >>}ʹ>!>5> '?>>SY>?N>v ?a>Bz?F%>>>5>f>P?L>?+>?Kw>s?>R>t >K>Q>@>(>>W>L>v>5y>>>>Z>o>f>>>V>@>b>¡>>G=>d$>2>y>8>R>B>a7>;>y>7>7>[>؊>]2>?>?;>y>~>y>R >rM> >->>k>%>>)>A>I>>->eV>+)?͢> .?jޡ>Z$?\>(&?>hw ?A>g ?ˏ>$?>?>.)?>-?3>31? >6v5?C>%8?dY>E/?gG>2?>w+?Z>'?>'/"?~W>t%?נ>*?ԋ>P-?p >D1?ӑ>,F5?H>*?B>a?>?R>K?+> 4?.>1y ?Y> ?\ȋ>??͊>a?vń>Y>*>>>?r>%?W> ?w>}>>Ps> >j>r>>2W>+M>>>>ע>c>/>t>>!w>>7>z>k>>,>1>w>'>D>>1>Eg>&>%$>>7S> >`> >>>$>>>3>}>F#>`>݁>>A>H>B> ?Ћ>>>>>x>4>V>$>>w>Y>x)>>8>>ro>>>P>>>q>>vÎ>z>ݨ>>>B><>?>>3j>ͼ>>Su>>7>S>>۸>MK>Ʒ>˩>m>be> >>!t>>%>>:@>>S>>ٴ> >:>ʓ>-">>P:>s>˝>֚> ?Cˢ> ?Ql>o?2>?m9>gF3?)>/?>9*?Y>,%?!>2 ?TW~>)[ ?P>?>X?<>R1?!ȉ>5?o>\5?>?T>_?s>?l>w-?q>M?|>?!{>u!?>A?}w>MJ?l{>g?m>0,?h>s? i>#?u>?X"?ak>4)?Uv>+'?Sq>.?p~>P,?eoy>24?>;5?ɒ>K!8?_>7?r>I5?g>4?b>k}!?Ub>}%?e>+?$k>/?o>42?>n>5.?h>(?i>$?l>?Hx>}%(?Hz>?.?l$>5?U>q/?;>>մ>>E>>>>V>a>0>HO>_>>U>>>â>)^>P>o>>>S>>>]>c>1>>M>w>> >5 >5F> >>>>\>>$}>>3>/>s>?Ȥ>>&>$> >8>/ۦ>aO>n3>@Q>(>:>K>n>>m>>_>/>J>9>l>>E>)_>N?M4?R?+?FU?+?U?{?|S?$?}Y?3"?X??\?G?4Z??\??p^\?BZ?]?[?]?ϡ?[?H?Z\?L?b]?c?[z\?=/?Z?.4?{\?N+?\?b-?'[?9-?2Z?31?X?y6?V?x*4?jZ?rm4?Z?3?O\?-?C^?'?d[^?T'?V[?"1?0H^?b1?c]?^1?QJ`?'?D^??(?=__?r?^?C!?]? ?\?h?>Z\?t!?]?%?;\?%?!]?`,?S\?/?O_?&?eV_??g+c? ?^?O>]??>v_?h>d\a?$>'c??a?7?̛[?mt>W?;8>~^?>=_?>a?Έ>`?T?^?>}`?w>U]?>Y?<>qU?,>yT?R>X?I>\?P>eVS?> Q?p >Q?F>tEQ? >0Q?>vQ?B#>tO?9>^P? >P?~>P?>"H?">aM?g>~M?>XL?d >?W%?>??˄???O>?QO? ? ?c?<?h??F??U???d"?>=!?>m&?>\ ?'>U?>m"?k>?>г?wg?[??\??-$??~ ?7>~??C?`v?){?c_? ?&?F??a?l?0?:w#? ?(?Y ?=*?W ??ۆ ?C #?Hp ?(?Q?|b!?Fa ?J+?f?#?] ?<.?i?er&?f ?%.??F'?r ?F-?4?kJ*?T?d&?(?du#?˿ ?]!?@ ?b"?5~ ?!?? ??2 ?:;?D?2?2?T#??+?t^?'??&?. ?$"?> ? %?-? ?F?O]?yY?R??s????G?F?Q?To%?'?d!?? ? ?I?"??,+?\W? +??{*?bL> ,?>r-??*,???VG*?L?;.?> /?\?D2?C?X:?>3?p?5?X>"6??\5??Y5?ߦ?KY6?m?^2?,,?j4?O?ys8?+h>9? A>Z/?>Ƣ)?d>)?b>zU/?r>L'?>y#? >[_(?J)>#?em>N,?>?>?u>?j>Y? >h?^>!?&>4)?0 >X ?mp>?>ٱ ? >9 ?G̬>y?kJ??"?ׇ?H?f?R"?*W?j#??h^&?B?%]'?0g?0G#??V"?>)?R>M?Ƈ>T?t>?V>0J?&>V?^I>:?>M?)>'?$>p?HQ>3?`>=? >|?W>s?>%?p>) ?>[B?)>?.t>٘ ?> ?<>t ?j>?>?o> ?8>h< ?>a ?>D?˜>z?#> ?,>y ?1> ?> ?|>0 ?E>G ?>}#?*?d ?r ? ??) ?( ? ?`???AD?ӽ??>/ ?k?A?I?W"?) >.?o>[?>>>,>r>G?>?->j>>>>>>>Y?>(?H>J >k>g>U>s>J>>r>>>j>)>A>>\u>>>&>:>J>>h>`>M>>6:>UN>u>> >NB?VD>s?>?P>^>C>k>t>>>a>v>>͏> ?> ?(}> ?>fJ?^>9?>?4>;n?Q>w?>w-?>? z>K?Lo>?(>??o> ?>o~>3>?x>ϟ?>$G ?> b?l>>@>i>)w>p&>>=>j> >>x) ?_>}?>>t>">1_>>o>>>w>C>W>Է>>c>?)?> ?J>!?_>p?>_?]>Y?>?> ?>\ ?r2>e ?$b>@>Ef>?/?A?o?U>?>?oG>>l>1>>>X>D>@m>)>$>>>9_>"> &>E,>[>>><>k>>Z>2>_>ʨ>j>0 >5>3>>Ag>rÿ>1>>'>>>>}>>>f><>xӵ>>>>>䄙>>囥>/>K>>ۜ>>>>>i>y?>>݌>B>>v>g>6>S>V>•>B$>m>5>Mf>Ů>YR>4>C=>A>K>o>Eg>|>CY>>d>>[>>@ڿ>">>>Z>[>> >r>R>(>u>w>p>J>o>@2>>:>b> >Ȫ>6>]ܞ>">>?X>@?nL>?Z> ?>4$??r1*?81 ?3? ?K'0I?>ܽL?>F?5>@?UN>hL?>N?L>O?\>Q?p>L?>ZI?>rmD?2>E?n>`B?kb>@?U>p>A?,>>?>??>Q??;q>R:?>B?@>¥C? >.C?_^>??R>:?{.>=:?!>2?>-/? 3>J9?<>0?V>4>?3>(0?>P?=>/O?%>FP?>P?X>WP?]o>\ Q?>)N?'>M?>@>oH?k>D?|D>C?:>J?bi>4H?>SC?*>C?>jJ?4>)%D?>C?>B?>E?> M?,>sJ?vn>QlM?j>@?lZ>[@?2ȭ>+??>1?.>$?@>%?0>E ?Y>r?>n?r>6?^>&S!?M>'?y!>-#=?9>3=?3>=?v>6?7ǩ>G-?>*?'>v5?˝>VE4?,>H?rk>l"?̲>y?)>$?%>#*?Pǫ>Y10?S>?+?֣>DO?9b%?H?Sw)?LqI?$*?PP?](?3U?q?KX??(Z??nlZ??V?@?V?;?!O?m#?fG?ԝ'?ST?;Q?X??S?}u?eY??S??s-V?ۅ?C[?\?1#X?#N??W?d? M?3l0?I?&7?fE?5E8?vPE?0?@?6?T'?p\*?+?2? G0?O.?(+?*?Ze"?,?ۢ?!3??X3?A?qW+?c?%?!?p!??M!?}?$?3?$?&?(?~ ? )??0?` ?/?8?q1??0?b? (?ӿ>(?N>$#?q>:u%?iT?$??Uk!??u!?0>ۤ.?>|*?G>V!?[>WB7?>c1?|`>&?L>(?:>S'? >,E*?)>v+?.>U3?J>-?PU>

.?{f>e1?'>~r8?>z3?]>t6?->dx8?>54?#>L1?>.?.>Y*?R>%?>Y ?c>Ov?>܃$?> W'?O>jg$?>?8>A$? >?yY>&?>'?>,)?m>n3-?_>7?7>785?ѫ>2?HS>P.?>'i*?H1>s?R>~;?y>Q?>O?E*>?Ϣ>? >N?>d?Qۖ>+L? >S?q>h ?^֔>=?m>i#?zS>H#?u>2!?1 >H$?>ND#?(C>$?>y ?@>0J?>E?#ٓ>Ӿ?>?>?D>`?L>8>c>>j>>[> ^>nL>>D>C>J>z>rD>|>]b>t>[>w>-">>'f}>"ƣ>5z>ί>t>{>J>A>>36>f>>xӝ>B>}>C>UN>s>>>F>M>#݇>?>YO>P>y>>xw>j>ur>">p>>N}>M>|>>Y>>׉>>>>ݖ>:>ũ>Ҩ>!>>>J>ڨ>v> ><>Z>3>g,>2v>]>;V>,>>%z>.> > >#/>>ms>>>`>c>b>>">S>c>>>sf>>~^>N+>r>b> >u9>_>Ƹ>V> >T>M>|> ]><>>~>j>>N >嶅>s>5 >q>y>>\> >j>>>d>|>i?>W?>?>?>-?>?沱> ?c>j??>|?˞>h?˙>Um?~>Zg?>?>B ?Ğ>? ?>?٧>7?> ?>?V>?E->?B̝>+?;™>G?⒓>k}?>2?>N?U>2 ?

?#? >d#?>[ !?o>N ?KV>?2s>h%?/>?[>ʌ'?<> '?>82#?ˣ>yt'?>&?o~>6&?->$?&S>@ ?]5>(?†>'?>?7>Z?C? -? oK?Yi?:?i?A?Z)?cH?A?:?=??D??r>?(?B? ?=^g?̗>k?8>j?h>Za?ͭ>d?X>HY?y>v]?>}>R?[>,V?8>0O?g{>+S?%>UO?U>ijT?>rS?K>DY? >H6[?s>>a?">c?>h?X>JB^?a>]?g>X?u>O?Þ?VJ?du>caH?j>^I?2r>S W?Z>o*N??N?d:?$T? ?ZT? ?K?6?8Q?z ?G?l?M?C?mB?*?I?G>?&"?'N6?)!$?{5?+?5??9?D?J???xC?&?=??2?}"?p1? '?T2?-{&?%F?x?E?y!???,&?xF?g?Š??%?X@?F@%?̘>?'?^=?#'?H?؞?oH?W&?:G?C?:?*?Z;?X*?D;?W*?)\;?(?Pq8?N'?l4?o*?2?*?3?B_*?U1?}"'?2?;(?;m5?*?;2?'?wL5?+?6?N*?=?*?ߌ>?U*?A?/*?"o=?t`-?.2?Q,?r.?](?P7?1?+>?S2?PE?&?TB?&?]@?8&?I?7!?G? ?"M?~!?9(Q?(?-\N??kN?2?BO??>K?e?J?m?t7?5?)a?>:i?R >k?>}b?˹>xz]?zõ>- `?]>x_?~>c?>Yg?uv>fIl?w>iu?=+>&y?/>}?AI>?>}?멵>~?┱>B@v?^>w?p> `? >wg?>\?u>>]?e>bc?Y4>n?,f>v?q>gw?>#q?>^?A> d?\>\?1>\?>`xa?}>g?F >"o? S>k?V}>([?>"b?D>:WX?q>Y?,D>_ \?#>mb?, >]pj?f>`j?_>ƾl?>(l?W>FD?F(>=?i >ܸE? >@?.=9?J=^+A?o= ;8?=5? >*7?>F'7?<3>vc~B?F>>:G?A->VE?a9>/E?C>h2?_$>2?>D?wI=,cC?=1@?Ps=A?Z=}=?k=GiB? |>EF??6>G@?=>?=|6?bf=A? >B?c>o>?D=5?>W^.?[=+,?& >O0?D=/?I=5_1?=Z2?=1?>=2?7>e0?.s=-?k=N,?=*?1>̙E?n<>C?,>%D?8>kI?J>R,G?:D>\UJ?g`T>MC?M>D?B]>cF?"?>PsJ?I>'NJ?M>CL?%S>G?`o>LOL?NFe>M?f1a>+M?c>1zN?g`>@M?wX>L?X>kK?S>2K?P>1AI?HI>wJ?W>M?ap>N?e>TUL?gj>I?y>J?F]>fJK?C>rH? *}>sH?1Bh>g G?w>MH?;>K?D0>KI?r>K?X>ʈG? >>F?>C?>P@?U>tE?ɂ>A?힄>@?{>??`k>k;?"U>9?ZB>5? B>59?Z>ZN?C>mQ?!>R?Ւ>fM?>XH?c>H?>ӅD?>H?_\>ZC?Ō>=?+>48?F>sH6?1>8?Sy>"@?L5s>LA?#h>JB?1Y>VA?/L>=A?D@?=6>@?,> =?_>0:?m>mt:?b> 6?0]>9?2G>A9?a;>D9?H.>:Z1?M>0?x> Q-?0|>&0?h>>2?6:W>4?kF> 5?i:>0?v>-?3l>N4?TR>g3?e>3?B*>72?9>i4?2B>7?ެ!>Â7?B>5?J>{6?U/O><6?`Z>6?M^>9?;Sh>=?%>:?č>7?QN>b5?[>t5?6?~> 2?]>=d6?t'h>{7?7m>68?ex>d\9?>t)6? >0? >Jy1?><>.?>CX-? >'i6?>L0?>}]6?=3k1?>8?">;?!? >4? >m6?>x&?7>"?5 >8%?>h#? >I"?.s>H"?>b ?=>-#?O>I#?g>2-?>+?Z>g*?o>b)?>Y(?,}>D(?F>L1?`#>U+?ދ>G$?>}\3?hϕ>6?B&>6?w>?qZ4?7p?KV-??9? ?6?<.?44?r?y:?A ?2?]>(?$>B+?>~t"?k>z!?>Rc&?|?'?$ ?3+?D?.??+1? ? 5?y>+?>O#)?|>z"?>|"?-^> ?=?j#?>Q'??3,?Q ?'?J?- ,?f?,?u?L0?Z?.? ?-?h"?{)?P>:? ?`2??͕1??-??'?.?$?( ?\0?1y?,.??{+?K9?j(???;:*? ?*??B*?Y?xc???g?9?t_?Ǡ?G`?Q>b??Ha? ?}Yb? ? \?/?h?!?j?=?Sf?,>$ m?>xf?Ef>|`?[>-k?? o?>k?H>j?>bil?%?&o??˿r??s??!v?Qg?Nt? ?g&p?$?f??n? ?q?-?t? ?}r??u?%?hxo??et?D&? r??n? ? k?u> ?o?t ?fq?9?qk?X ?*o?d? ql??`l?>e?DL>:e?>.g?.>]4h?>/e?'>4ic?8>fKb? >d?LR>b?>c?]? 'h?b>h?$>Ed?I?dl??zqj?vP%?p|q?c(? -o?Qh)?Bk?3+?W|k?',?jf?[#"?k?)_(?Ja?zQ/?kb?)"'?vc?u?p@g?(?f?#?g?!)?_c?.?մ_?M)? b?$?d?s?0h??kg?~ ?i? ?j?o?Pk??wj??0k? ?k?z?Ei?S>ci?>f?>Pj?>,}d??f?o.?qUe?ܺ'?s`? ? j?1?l? ?tj?>?Kn?3?Qk? ?n?Ѱ?Ke?If%?c?V(?q!_?YM?__g??n?TSn??q?bK>Bo?d?KYr?N? n?9?s?4>@/x?{>6j?>;j?LP>˟c?m>]? y>+_?>Eg?֐>{^?ȗ>rb?q>5yb?[>]?> ]?^>2u[?;6>YZ?>ղ]?_>c?\>>\?>X?>Z?>Nc?P>_?U>c?[>Ng?w->n?3k> e?>>l?&r>F_?>W? > S?y>ݴq?!>at?>Ęp?b>Lw?>|?F#>|?Gw>fx?>??*?o?R?N?/y?3?6w??ct??t{?A?2z?Y3?pp?I?l u? ?AJt?`?ŭz??Dw?%?`[w?c-?ƥr?w"?#t?$?Yo?@Q)?W@q?r.?k?y-? s?c2?Un?I0?{o? 44?j?"59?h?u=?}e?3?.g? {.?l?1'4?i?T??a?^i=?9a?:?kd?;?@MY?S=?`W?~9?`]?7/?>b?3?/`?2?ci?(?ol?N/?a?Q7?Z?=:?IMW?-?rn[??.k??{b??Cd??\c?6?rb?!? `?>%?b??a? ?<_?D?^`?K ?e?S?0c?;8?h?D"?Rc?O@+?]?]%?\?l(?Z?7-?^Y?W4?2Y?>]?5?]?? 0`?# ?X??X??T?9>[U?ӣ>X?Z?{g\?XU?,HW?^?b0W?ӆ?\?= ?\?j?]?>a? > ]?X>X?>[?>_V?0/>iS? >S?cz>R?`?J{S?>nR?.>U?\=>xE\?>p(T?O>zV?kE>cW?0>N__?V=?=? #<;?jK<? )=?|=5?="?rD=<>˹3>qa<>;)>o<|)>G)Z=g?>? &>DŽ?{=X?f>|?>_>'>>n>$^?MN>O?OO>>N>B>G>3>PM>fi>$K>U>` 8> {>ÞF>3>nL>T>SL>>OI>x>KE>S>)>>CS6>,>1>G>M<>|>L>>&%>3>q>>9>N >D>E.>TSB>L>FD>)?:>yx? 2>#?9->:y>=E/?};?ݙ<}?=!?`=?J=m>C;'= >!a=MK>=6>=1?>=t@>X=H?=?J={?>8? >)v>r5=>=y>Ac=>w=3>c=>=>~=Z>˱=29>=>=>Љ=)>]=-Z>\9=lz>̍=)>.>C?0B>0?4>Y?SF>?V}>5B>&>$>, >ys>=>:>>Z>U>&(>[}> :>[?M> ?'?>>p=!>g)=>_ =,(>=j>Hc=">ҧ<>OuH<?vx<> <>=>b;?L;>?q@=?>:<\>WvA<(E>T <\>#2;G>9 ?t?_=>1@shQ=I>d =8>'+3<>W=>2[=>ݱ=qV>Ѧ=,?I-T=8>>X>+2z=>_>#?>#%?=N#?hf>?Kg>?Y>?ʊa<{??;?ҫ;6?m <_>˵;%?ƭ<#?= S>8H=> =9>ȯ=5&>L3=du>/=0>E+=>5=N>=>4>{?x>:?=?5>>77>k> >B>5>k>->Z>=>2=>Dm=`?>=~>N)>F?/=?=>C=_ >O])>u> 7>xA>K=j>^,=?9 >.?>?vkmark-2017.08+git20220909/data/models/kmscube.ply000066400000000000000000000007271441550741700211660ustar00rootroot00000000000000ply format ascii 1.0 comment PLY version of kmscube element vertex 8 property float x property float y property float z property float red property float green property float blue element face 6 property list uchar int vertex_index end_header -1.0 +1.0 +1.0 0 0 1 -1.0 -1.0 +1.0 0 1 1 +1.0 -1.0 +1.0 1 1 1 +1.0 +1.0 +1.0 1 0 1 +1.0 +1.0 -1.0 1 0 0 +1.0 -1.0 -1.0 1 1 0 -1.0 -1.0 -1.0 0 1 0 -1.0 +1.0 -1.0 0 0 0 4 0 1 2 3 4 4 5 6 7 4 3 2 5 4 4 7 6 1 0 4 7 0 3 4 4 1 6 5 2 vkmark-2017.08+git20220909/data/shaders/000077500000000000000000000000001441550741700171475ustar00rootroot00000000000000vkmark-2017.08+git20220909/data/shaders/desktop.frag000066400000000000000000000004071441550741700214620ustar00rootroot00000000000000#version 420 core layout(binding = 1) uniform sampler2D MaterialTexture0; layout(location = 0) in vec2 in_texcoord; layout(location = 0) out vec4 frag_color; void main(void) { vec4 texel = texture(MaterialTexture0, in_texcoord); frag_color = texel; } vkmark-2017.08+git20220909/data/shaders/desktop.frag.spv000066400000000000000000000012101441550741700222620ustar00rootroot00000000000000# GLSL.std.450mainmain texel MaterialTexture0in_texcoordfrag_colorG "G !GG!     ;  ; ;6; =  =W> = >8vkmark-2017.08+git20220909/data/shaders/desktop.vert000066400000000000000000000005211441550741700215200ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { uniform mat4 Transform; }; layout(location = 0) in vec2 in_position; layout(location = 1) in vec2 in_texcoord; layout(location = 0) out vec2 out_texcoord; void main(void) { gl_Position = Transform * vec4(in_position, 0.0, 1.0); out_texcoord = in_texcoord; } vkmark-2017.08+git20220909/data/shaders/desktop.vert.spv000066400000000000000000000023001441550741700223240ustar00rootroot00000000000000#' GLSL.std.450 main $%main gl_PerVertex gl_Position gl_PointSize gl_ClipDistance blockTransformin_position$out_texcoord%in_texcoordH H  H  G HH#HGG"G!GG$G%!  +       ;  + ;  ;++? ! #;#$;%6A==QQP A!" >" =&%>$&8vkmark-2017.08+git20220909/data/shaders/effect2d-blur.frag000066400000000000000000000044111441550741700224340ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { float TextureStepX; float TextureStepY; }; layout(binding = 1) uniform sampler2D Texture0; layout(location = 0) in vec2 in_texcoord; layout(location = 0) out vec4 frag_color; const float Kernel0 = 0.066667; const float Kernel1 = 0.066667; const float Kernel2 = 0.066667; const float Kernel3 = 0.066667; const float Kernel4 = 0.066667; const float Kernel5 = 0.066667; const float Kernel6 = 0.066667; const float Kernel7 = 0.066667; const float Kernel8 = 0.066667; const float Kernel9 = 0.066667; const float Kernel10 = 0.066667; const float Kernel11 = 0.066667; const float Kernel12 = 0.066667; const float Kernel13 = 0.066667; const float Kernel14 = 0.066667; void main(void) { vec4 result; result = texture(Texture0, in_texcoord + vec2(-2.0 * TextureStepX, -1.0 * TextureStepY)) * Kernel0 + texture(Texture0, in_texcoord + vec2(-1.0 * TextureStepX, -1.0 * TextureStepY)) * Kernel1 + texture(Texture0, in_texcoord + vec2(0.0 * TextureStepX, -1.0 * TextureStepY)) * Kernel2 + texture(Texture0, in_texcoord + vec2(1.0 * TextureStepX, -1.0 * TextureStepY)) * Kernel3 + texture(Texture0, in_texcoord + vec2(2.0 * TextureStepX, -1.0 * TextureStepY)) * Kernel4 + texture(Texture0, in_texcoord + vec2(-2.0 * TextureStepX, 0.0 * TextureStepY)) * Kernel5 + texture(Texture0, in_texcoord + vec2(-1.0 * TextureStepX, 0.0 * TextureStepY)) * Kernel6 + texture(Texture0, in_texcoord + vec2(0.0 * TextureStepX, 0.0 * TextureStepY)) * Kernel7 + texture(Texture0, in_texcoord + vec2(1.0 * TextureStepX, 0.0 * TextureStepY)) * Kernel8 + texture(Texture0, in_texcoord + vec2(2.0 * TextureStepX, 0.0 * TextureStepY)) * Kernel9 + texture(Texture0, in_texcoord + vec2(-2.0 * TextureStepX, 1.0 * TextureStepY)) * Kernel10 + texture(Texture0, in_texcoord + vec2(-1.0 * TextureStepX, 1.0 * TextureStepY)) * Kernel11 + texture(Texture0, in_texcoord + vec2(0.0 * TextureStepX, 1.0 * TextureStepY)) * Kernel12 + texture(Texture0, in_texcoord + vec2(1.0 * TextureStepX, 1.0 * TextureStepY)) * Kernel13 + texture(Texture0, in_texcoord + vec2(2.0 * TextureStepX, 1.0 * TextureStepY)) * Kernel14; frag_color = vec4(result.xyz, 1.0); } vkmark-2017.08+git20220909/data/shaders/effect2d-blur.frag.spv000066400000000000000000000112041441550741700232410ustar00rootroot00000000000000# GLSL.std.450mainmain result Texture0in_texcoordblockTextureStepXTextureStepYfrag_colorG "G !GH#H#GG"G!G!     ;  ;+ ; + +++%=+6+D?+R@ ;6; =  =A=A= ! P"!#"W$#&$%= ' =(A)=*)+*A,=-,.-P/+.0(/W1'021%3&2= 4 =5A7=87968A:=;:<;P=9<>5=W?4>@?%A3@= B =CAE=FEGDFAH=IHJIPKGJLCKWMBLNM%OAN= P =QAS=TSURTAV=WVXWPYUXZQYW[PZ\[%]O\= ^ =_A`=a`baAc=dce6dPfbeg_fWh^gih%j]i= k =lAm=nmonAp=qpr6qPsortlsWuktvu%wjv= x =yAz={z|6{A}=~}6~P|yWx%w= =A=DA=6PW%= =A=RA=6PW%= =A=A=DPW%= =A=A=DPW%= =A=6A=DPW%= =A=DA=DPW%= =A=RA=DPW%> = OQQQPD>8vkmark-2017.08+git20220909/data/shaders/effect2d-edge.frag000066400000000000000000000027571441550741700224070ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { float TextureStepX; float TextureStepY; }; layout(binding = 1) uniform sampler2D Texture0; layout(location = 0) in vec2 in_texcoord; layout(location = 0) out vec4 frag_color; const float Kernel0 = 0.000000; const float Kernel1 = 0.250000; const float Kernel2 = 0.000000; const float Kernel3 = 0.250000; const float Kernel4 = -1.000000; const float Kernel5 = 0.250000; const float Kernel6 = 0.000000; const float Kernel7 = 0.250000; const float Kernel8 = 0.000000; void main(void) { vec4 result; result = texture(Texture0, in_texcoord + vec2(-1.0 * TextureStepX, -1.0 * TextureStepY)) * Kernel0 + texture(Texture0, in_texcoord + vec2(0.0 * TextureStepX, -1.0 * TextureStepY)) * Kernel1 + texture(Texture0, in_texcoord + vec2(1.0 * TextureStepX, -1.0 * TextureStepY)) * Kernel2 + texture(Texture0, in_texcoord + vec2(-1.0 * TextureStepX, 0.0 * TextureStepY)) * Kernel3 + texture(Texture0, in_texcoord + vec2(0.0 * TextureStepX, 0.0 * TextureStepY)) * Kernel4 + texture(Texture0, in_texcoord + vec2(1.0 * TextureStepX, 0.0 * TextureStepY)) * Kernel5 + texture(Texture0, in_texcoord + vec2(-1.0 * TextureStepX, 1.0 * TextureStepY)) * Kernel6 + texture(Texture0, in_texcoord + vec2(0.0 * TextureStepX, 1.0 * TextureStepY)) * Kernel7 + texture(Texture0, in_texcoord + vec2(1.0 * TextureStepX, 1.0 * TextureStepY)) * Kernel8; frag_color = vec4(result.xyz, 1.0); } vkmark-2017.08+git20220909/data/shaders/effect2d-edge.frag.spv000066400000000000000000000062541441550741700232120ustar00rootroot00000000000000# GLSL.std.450mainmain result Texture0in_texcoordblockTextureStepXTextureStepYfrag_colorG "G !GH#H#GG"G!G!     ;  ;+ ; + ++$+1>+6? ;6; =  =A=A= P! "!W#"%#$= & ='A(=)(*$)A+=,+-,P.*-/'.W0&/2013%2= 4 =5A7=87968A:=;:<;P=9<>5=W?4>@?$A3@= B =CAD=EDFEAG=HGI$HPJFIKCJWLBKML1NAM= O =PAQ=RQS$RAT=UTV$UPWSVXPWWYOXZY[NZ= \ =]A^=_^`6_Aa=bac$bPd`ce]dWf\egf1h[g= i =jAk=lkmlAn=onp6oPqmprjqWsirts$uht= v =wAx=yxz$yA{=|{}6|P~z}w~Wv1u= =A=6A=6PW$> = OQQQP6>8vkmark-2017.08+git20220909/data/shaders/effect2d-none.frag000066400000000000000000000005001441550741700224220ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { float TextureStepX; float TextureStepY; }; layout(binding = 1) uniform sampler2D Texture0; layout(location = 0) in vec2 in_texcoord; layout(location = 0) out vec4 frag_color; void main(void) { frag_color = texture(Texture0, in_texcoord); } vkmark-2017.08+git20220909/data/shaders/effect2d-none.frag.spv000066400000000000000000000014141441550741700232360ustar00rootroot00000000000000# GLSL.std.450main main frag_color Texture0in_texcoordblockTextureStepXTextureStepYG G "G !GH#H#GG"G!!  ;    ;  ; ;6=  =W> 8vkmark-2017.08+git20220909/data/shaders/effect2d.vert000066400000000000000000000003711441550741700215340ustar00rootroot00000000000000#version 420 core layout(location = 0) in vec2 in_position; layout(location = 1) in vec2 in_texcoord; layout(location = 0) out vec2 out_texcoord; void main(void) { gl_Position = vec4(in_position, 0.0, 1.0); out_texcoord = in_texcoord; } vkmark-2017.08+git20220909/data/shaders/effect2d.vert.spv000066400000000000000000000016441441550741700223470ustar00rootroot00000000000000# GLSL.std.450 main main gl_PerVertex gl_Position gl_PointSize gl_ClipDistance in_positionout_texcoordin_texcoordH H  H  G GGG!  +       ;  + ;++?  ;;6=QQPA >=>8vkmark-2017.08+git20220909/data/shaders/light-advanced.frag000066400000000000000000000031661441550741700226700ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { uniform mat4 ModelViewProjectionMatrix; uniform mat4 NormalMatrix; uniform vec4 MaterialDiffuse; }; layout(location = 0) in vec3 in_normal; layout(location = 0) out vec4 frag_color; void main(void) { const vec4 LightSourceAmbient = vec4(0.011, 0.011, 0.011, 1.0); const vec4 LightSourceDiffuse = vec4(0.8, 0.8, 0.8, 1.0); const vec4 LightSourceSpecular = vec4(0.8, 0.8, 0.8, 1.0); const vec4 MaterialAmbient = vec4(1.0, 1.0, 1.0, 1.0); const vec4 MaterialSpecular = vec4(1.0, 1.0, 1.0, 1.0); const float MaterialShininess = 250.0; const vec4 LightSourcePosition = vec4(20.0, -20.0, 10.0, 1.0); const vec3 LightSourceHalfVector = vec3(0.40824, -0.40824, 0.81649); vec3 N = normalize(in_normal); // In the lighting model we are using here (Blinn-Phong with light at // infinity, viewer at infinity), the light position/direction and the // half vector is constant for the all the fragments. vec3 L = normalize(LightSourcePosition.xyz); vec3 H = normalize(LightSourceHalfVector); // Calculate the diffuse color according to Lambertian reflectance vec4 diffuse = MaterialDiffuse * LightSourceDiffuse * max(dot(N, L), 0.0); // Calculate the ambient color vec4 ambient = MaterialAmbient * LightSourceAmbient; // Calculate the specular color according to the Blinn-Phong model vec4 specular = MaterialSpecular * LightSourceSpecular * pow(max(dot(N,H), 0.0), MaterialShininess); // Calculate the final color frag_color = vec4((ambient + specular + diffuse).xyz, 1.0); } vkmark-2017.08+git20220909/data/shaders/light-advanced.frag.spv000066400000000000000000000035501441550741700234740ustar00rootroot00000000000000#E GLSL.std.450main :main N in_normalLHdiffuseblock ModelViewProjectionMatrixNormalMatrixMaterialDiffuse.ambient1specular:frag_colorG HH#HHH#@HH#GG"G!G:!    ; +*?+*+>,+|>+|Ѿ+#Q?,  ; +  !+$L?+%?,&$$$%+++/X94<,0///%+6zC 9;9:6; ;;;;.;1=  E > >>A!" =#"'#&=( =)*() ,(*+-',>->.0=2 =3423 5(4+ 7568&7>18=;.=<1=;<=>?=>O@??QA@QB@QC@PDABC%>:D8vkmark-2017.08+git20220909/data/shaders/light-advanced.vert000066400000000000000000000010611441550741700227210ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { uniform mat4 ModelViewProjectionMatrix; uniform mat4 NormalMatrix; uniform vec4 MaterialDiffuse; }; layout(location = 0) in vec3 in_position; layout(location = 1) in vec3 in_normal; layout(location = 0) out vec3 out_normal; void main(void) { // Transform the normal to eye coordinates out_normal = normalize(vec3(NormalMatrix * vec4(in_normal, 1.0))); // Transform the position to clip coordinates gl_Position = ModelViewProjectionMatrix * vec4(in_position, 1.0); } vkmark-2017.08+git20220909/data/shaders/light-advanced.vert.spv000066400000000000000000000031501441550741700235310ustar00rootroot00000000000000#4 GLSL.std.450 main '+main out_normal block ModelViewProjectionMatrix NormalMatrix MaterialDiffusein_normal%gl_PerVertex%gl_Position%gl_PointSize%gl_ClipDistance'+in_positionG H H #H H H #@H H #G G"G!GH% H% H% G%G+!  ;      ;  +  ;+?" +"#$#% $ &%;&'+(;+ 2 6A= =QQQP  QQQP  !E > !A)(= *)=,+Q-,Q.,Q/,P 0-./ 1*0A23'(>318vkmark-2017.08+git20220909/data/shaders/light-basic-tex.frag000066400000000000000000000004711441550741700227760ustar00rootroot00000000000000#version 420 core layout(binding = 1) uniform sampler2D MaterialTexture0; layout(location = 0) in vec4 in_color; layout(location = 1) in vec2 in_texcoord; layout(location = 0) out vec4 frag_color; void main(void) { vec4 texel = texture(MaterialTexture0, in_texcoord); frag_color = texel * in_color; } vkmark-2017.08+git20220909/data/shaders/light-basic-tex.frag.spv000066400000000000000000000013641441550741700236070ustar00rootroot00000000000000# GLSL.std.450mainmain texel MaterialTexture0in_texcoordfrag_colorin_colorG "G !GGG!     ;  ; ; ;6; =  =W> = =>8vkmark-2017.08+git20220909/data/shaders/light-basic-tex.vert000066400000000000000000000021551441550741700230400ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { uniform mat4 ModelViewProjectionMatrix; uniform mat4 NormalMatrix; uniform vec4 MaterialDiffuse; }; layout(location = 0) in vec3 in_position; layout(location = 1) in vec3 in_normal; layout(location = 2) in vec2 in_texcoord; layout(location = 0) out vec4 out_color; layout(location = 1) out vec2 out_texcoord; vec4 LightSourcePosition = vec4(20.0, -20.0, 10.0, 1.0); void main(void) { // Transform the normal to eye coordinates vec3 N = normalize(vec3(NormalMatrix * vec4(in_normal, 1.0))); // The LightSourcePosition is actually its direction for directional light vec3 L = normalize(LightSourcePosition.xyz); // Multiply the diffuse value by the vertex color (which is fixed in this case) // to get the actual color that we will use to draw this vertex with float diffuse = max(dot(N, L), 0.0); out_color = vec4(diffuse * MaterialDiffuse.rgb, MaterialDiffuse.a); out_texcoord = in_texcoord; // Transform the position to clip coordinates gl_Position = ModelViewProjectionMatrix * vec4(in_position, 1.0); } vkmark-2017.08+git20220909/data/shaders/light-basic-tex.vert.spv000066400000000000000000000050301441550741700236420ustar00rootroot00000000000000#[ GLSL.std.450 main4GIOSmain LightSourcePositionNblock ModelViewProjectionMatrixNormalMatrixMaterialDiffusein_normal(L-diffuse4out_colorGout_texcoordIin_texcoordMgl_PerVertexMgl_PositionMgl_PointSizeMgl_ClipDistanceOSin_positionHH#HHH#@HH#GG"G!GG4GGGIHM HM HM GMGS!  ; + A+ + A+ ?,   ; +  ; ,+1 3;34+6 7< +<= >E FE;FG HE;HI+<KLKML NM;NO+P;S6;;(;,-> A==QQQ P! "!Q#"Q$"Q%"P&#$% 'E&>'=) O*)) +E*>(+=.=/(0./ 2(01>-2=5-A786=98O:99;:5A>?6==@?QA;QB;QC;PDABC@>4D=EJI>GJAQP=RQ=TSQUTQVTQWTPXUVW YRXA3ZOP>ZY8vkmark-2017.08+git20220909/data/shaders/light-basic.frag000066400000000000000000000002251441550741700221750ustar00rootroot00000000000000#version 420 core layout(location = 0) in vec4 in_color; layout(location = 0) out vec4 frag_color; void main(void) { frag_color = in_color; } vkmark-2017.08+git20220909/data/shaders/light-basic.frag.spv000066400000000000000000000005741441550741700230130ustar00rootroot00000000000000#  GLSL.std.450main main frag_color in_colorG G !  ;   ; 6= > 8vkmark-2017.08+git20220909/data/shaders/light-basic.vert000066400000000000000000000017661441550741700222510ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { uniform mat4 ModelViewProjectionMatrix; uniform mat4 NormalMatrix; uniform vec4 MaterialDiffuse; }; layout(location = 0) in vec3 in_position; layout(location = 1) in vec3 in_normal; layout(location = 0) out vec4 out_color; vec4 LightSourcePosition = vec4(20.0, -20.0, 10.0, 1.0); void main(void) { // Transform the normal to eye coordinates vec3 N = normalize(vec3(NormalMatrix * vec4(in_normal, 1.0))); // The LightSourcePosition is actually its direction for directional light vec3 L = normalize(LightSourcePosition.xyz); // Multiply the diffuse value by the vertex color (which is fixed in this case) // to get the actual color that we will use to draw this vertex with float diffuse = max(dot(N, L), 0.0); out_color = vec4(diffuse * MaterialDiffuse.rgb, MaterialDiffuse.a); // Transform the position to clip coordinates gl_Position = ModelViewProjectionMatrix * vec4(in_position, 1.0); } vkmark-2017.08+git20220909/data/shaders/light-basic.vert.spv000066400000000000000000000045301441550741700230500ustar00rootroot00000000000000#U GLSL.std.450 main4IMmain LightSourcePositionNblock ModelViewProjectionMatrixNormalMatrixMaterialDiffusein_normal(L-diffuse4out_colorGgl_PerVertexGgl_PositionGgl_PointSizeGgl_ClipDistanceIMin_positionHH#HHH#@HH#GG"G!GG4HG HG HG GGGM!  ; + A+ + A+ ?,   ; +  ; ,+1 3;34+6 7< +<= >+<EFEGF HG;HI+J;M6;;(;,-> A==QQQ P! "!Q#"Q$"Q%"P&#$% 'E&>'=) O*)) +E*>(+=.=/(0./ 2(01>-2=5-A786=98O:99;:5A>?6==@?QA;QB;QC;PDABC@>4DAKJ=LK=NMQONQPNQQNPROPQ SLRA3TIJ>TS8vkmark-2017.08+git20220909/data/shaders/light-cel.frag000066400000000000000000000047621441550741700216710ustar00rootroot00000000000000#version 420 core layout(location = 0) in vec3 in_normal; layout(location = 1) in vec4 in_position; layout(location = 0) out vec4 frag_color; void main(void) { const vec4 OutlineColor = vec4(0.0, 0.0, 0.0, 1.0); const vec2 OutlineThickness = vec2(0.1, 0.4); const vec4 BaseColor = vec4(0.0, 0.08, 0.0, 1.0); const vec4 LightColor = vec4(1.0, 1.0, 1.0, 1.0); const vec4 LightSourcePosition = vec4(4.0, -3.0, 1.0, 1.0); const vec4 DiffuseColor = vec4(0.0, 0.35, 0.0, 1.0); const vec4 SpecularColor = vec4(0.8, 0.8, 0.8, 0.7); const float DiffuseThreshold = 0.1; const float SpecularThreshold = 0.5; const float Shininess = 10.0; // Initialize the fragment color with an unlit value. vec4 fragColor = BaseColor; // Set up factors for computing diffuse illumination vec3 vertex_light = LightSourcePosition.xyz - in_position.xyz; vec3 N = normalize(in_normal); vec3 L = normalize(vertex_light); float NdotL = dot(N, L); float maxNdotL = max(NdotL, 0.0); float attenuation = length(LightSourcePosition) / length(vertex_light); // See if we have a diffuse contribution... // This will only be true if the interpolated normal and the light // are pointing in the "same" direction, and the attenuation due to // distance allows enough light for diffuse reflection. if (attenuation * maxNdotL >= DiffuseThreshold) { fragColor = LightColor * DiffuseColor; } // See if this fragment is part of the silhouette // If it is facing away from the viewer enough not to get any // diffuse illumination contribution, then it is close enough // to the silouhette to be painted with the outline color rather // than the unlit color. vec3 V = normalize(-in_position.xyz); if (dot(V, N) < mix(OutlineThickness.x, OutlineThickness.y, maxNdotL)) { fragColor = LightColor * OutlineColor; } // See if we have a specular contribution... // If the interpolated normal direction and the light direction // are facing the "same" direction, and the attenuated specular // intensity is strong enough, then we have a contribution. vec3 R = reflect(-L, N); float specularIntensity = pow(max(0.0, dot(R, V)), Shininess); if (NdotL > 0.0 && attenuation * specularIntensity > SpecularThreshold) { fragColor = SpecularColor.a * LightColor * SpecularColor + (1.0 - SpecularColor.a) * fragColor; } // Emit the final color frag_color = vec4(fragColor.xyz, 1.0); } vkmark-2017.08+git20220909/data/shaders/light-cel.frag.spv000066400000000000000000000051501441550741700224700ustar00rootroot00000000000000#n GLSL.std.450maingmain fragColorvertex_lightin_positionNin_normalL"NdotL&maxNdotL)attenuation8VGRLspecularIntensitygfrag_colorGGGg!  + + ף=+ ?,  +@+@, ; ; !+*F@+1=2+633>,7 6 +@>,F +Q A+Z?+_)\?+`H>,a___`+b> f;fg6; ;;;;!";!&;!);8;G;!L> =O>= E>=  E> =#=$%#$>"%='" ((' >&(=+ ,B+-*,>)-=.)=/&0./230153454> 755=9O:99;: <E;>8<==8=>?=>=A& B.1@A2C?BECDED> FEE=HIH=J KGIJ>GK=MG=N8OMN P( O RPQ>LR=S"2TS VTUVU=W)=XLYWX2[YZVV2\TE[U^\]^]=c dcbead> e^^=h OihhQjiQkiQliPmjkl >gm8vkmark-2017.08+git20220909/data/shaders/light-phong.frag000066400000000000000000000031411441550741700222270ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { uniform mat4 ModelViewProjectionMatrix; uniform mat4 NormalMatrix; uniform vec4 MaterialDiffuse; uniform mat4 ModelViewMatrix; }; layout(location = 0) in vec3 in_normal; layout(location = 1) in vec4 in_position; layout(location = 0) out vec4 frag_color; vec4 compute_color(vec4 light_position, vec4 diffuse_light_color) { const vec4 lightAmbient = vec4(0.011, 0.011, 0.011, 1.0); const vec4 lightSpecular = vec4(0.8, 0.8, 0.8, 1.0); const vec4 matAmbient = vec4(0.2, 0.2, 0.2, 1.0); const vec4 matSpecular = vec4(1.0, 1.0, 1.0, 1.0); const float matShininess = 250.0; vec3 eye_direction = normalize(-in_position.xyz); vec3 light_direction = normalize(light_position.xyz/light_position.w - in_position.xyz/in_position.w); vec3 normalized_normal = normalize(in_normal); vec3 reflection = reflect(-light_direction, normalized_normal); float specularTerm = pow(max(0.0, dot(reflection, eye_direction)), matShininess); float diffuseTerm = max(0.0, dot(normalized_normal, light_direction)); vec4 specular = (lightSpecular * matSpecular); vec4 ambient = (lightAmbient * matAmbient); vec4 diffuse = (diffuse_light_color * MaterialDiffuse); vec4 result = (specular * specularTerm) + ambient + (diffuse * diffuseTerm); return result; } void main(void) { const vec4 LightSourcePosition = vec4(20.0, -20.0, 10.0, 1.0); const vec4 LightSourceDiffuse = vec4(0.8, 0.8, 0.8, 1.0); frag_color = compute_color(LightSourcePosition, LightSourceDiffuse); } vkmark-2017.08+git20220909/data/shaders/light-phong.frag.spv000066400000000000000000000056341441550741700230470ustar00rootroot00000000000000#j GLSL.std.450main,bmain compute_color(vf4;vf4; light_position diffuse_light_coloreye_directionin_positionlight_direction*normalized_normal,in_normal/reflection4specularTerm<diffuseTermAspecularEambientHdiffuseKblock KModelViewProjectionMatrixKNormalMatrixKMaterialDiffuseKModelViewMatrixMTresultbfrag_colorgparamhparamGG,HKHK#HKHKHK#@HKHK#HKHK#HKGKGM"GM!Gb!  !   ; +  # +;+,+5+:zC+BL?+C?,DBBBC+F-;,GFFFCJKJJJ LK;LMN +NO P a;ab+cA+d+e A,fcdeC6;g;h>gf>hD9i gh>bi86 7 7  ;;;*;/;4;<;A;E;H;T=O E>= OA =P =!O"!!A#$=%$P&%%%'"&( ' )E(>)=-, .E->*.=010=2* 3G12>/3=6/=7867 9(58 ;9:>4;==*=>?=> @(5?><@>AD>EG=I APQMO=RQSIR>HS=UA=V4WUV=XEYWX=ZH=[<\Z[]Y\>T]=^T^8vkmark-2017.08+git20220909/data/shaders/light-phong.vert000066400000000000000000000014471441550741700222770ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { uniform mat4 ModelViewProjectionMatrix; uniform mat4 NormalMatrix; uniform vec4 MaterialDiffuse; uniform mat4 ModelViewMatrix; }; layout(location = 0) in vec3 in_position; layout(location = 1) in vec3 in_normal; layout(location = 0) out vec3 out_normal; layout(location = 1) out vec4 out_position; void main(void) { vec4 current_position = vec4(in_position, 1.0); // Transform the normal to eye coordinates out_normal = normalize(vec3(NormalMatrix * vec4(in_normal, 1.0))); // Transform the current position to eye coordinates out_position = ModelViewMatrix * current_position; // Transform the current position to clip coordinates gl_Position = ModelViewProjectionMatrix * current_position; } vkmark-2017.08+git20220909/data/shaders/light-phong.vert.spv000066400000000000000000000036701441550741700231060ustar00rootroot00000000000000#= GLSL.std.450 main +6main current_position in_positionout_normalblock ModelViewProjectionMatrixNormalMatrixMaterialDiffuseModelViewMatrixin_normal+out_position4gl_PerVertex4gl_Position4gl_PointSize4gl_ClipDistance6G GHH#HHH#@HH#HH#HGG"G!GG+H4 H4 H4 G4!      ; +?  ; ; + ;  *;*++,1 +123243 54;56+76; = Q Q Q P> A== Q Q!Q"P# !"$#Q%$Q&$Q'$P (%&'  )E(>)A-,=.-=/ 0./>+0A87=98=: ;9:A*<67><;8vkmark-2017.08+git20220909/data/shaders/vkcube.frag000066400000000000000000000002241441550741700212650ustar00rootroot00000000000000#version 420 core layout(location = 0) in vec4 vVaryingColor; layout(location = 0) out vec4 f_color; void main() { f_color = vVaryingColor; } vkmark-2017.08+git20220909/data/shaders/vkcube.frag.spv000066400000000000000000000005741441550741700221040ustar00rootroot00000000000000#  GLSL.std.450main main f_color vVaryingColorG G !  ;   ; 6= > 8vkmark-2017.08+git20220909/data/shaders/vkcube.vert000066400000000000000000000014461441550741700213350ustar00rootroot00000000000000#version 420 core layout(std140, binding = 0) uniform block { uniform mat4 modelviewMatrix; uniform mat4 modelviewprojectionMatrix; uniform mat4 normalMatrix; }; layout(location = 0) in vec4 in_position; layout(location = 1) in vec4 in_color; layout(location = 2) in vec3 in_normal; vec4 lightSource = vec4(2.0, 2.0, 20.0, 0.0); layout(location = 0) out vec4 vVaryingColor; void main() { gl_Position = modelviewprojectionMatrix * in_position; vec3 vEyeNormal = vec3(normalMatrix * vec4(in_normal, 1.0)); vec4 vPosition4 = modelviewMatrix * in_position; vec3 vPosition3 = vPosition4.xyz / vPosition4.w; vec3 vLightDir = normalize(lightSource.xyz - vPosition3); float diff = max(0.0, dot(vEyeNormal, vLightDir)); vVaryingColor = vec4(diff * in_color.rgb, 1.0); } vkmark-2017.08+git20220909/data/shaders/vkcube.vert.spv000066400000000000000000000051201441550741700221350ustar00rootroot00000000000000#[ GLSL.std.450 main+QSmain lightSourcegl_PerVertexgl_Positiongl_PointSizegl_ClipDistanceblockmodelviewMatrix modelviewprojectionMatrixnormalMatrixin_position&vEyeNormal+in_normal8vPosition4=vPosition3FvLightDirLdiffQvVaryingColorSin_colorH H H GHH#HHH#@HHH#HGG"G!GG+GQGS!  ; + @+ A+ ,  + ; + ;+  ; "$ %$+' *$;*++-? 7+@ A;"Q;S6;%&;78;%=;%F;AL> A== ! A"#>#!A('=)(=$,+Q.,Q/,Q0,P1./0-2)1Q32Q42Q52P$6345>&6A9=:9=;<:;>8<=>8O$?>>AAB8@=CBP$DCCC$E?D>=E=G O$HGG=$I=$JHI $KEJ>FK=$M&=$NFOMN P( O>LP=RL=TSO$UTT$VURQWVQXVQYVPZWXY->QZ8vkmark-2017.08+git20220909/data/textures/000077500000000000000000000000001441550741700174015ustar00rootroot00000000000000vkmark-2017.08+git20220909/data/textures/crate-base.jpg000066400000000000000000003017071441550741700221210ustar00rootroot00000000000000JFIFHHC     C    Ck$HzjDU@#qAlKK=;E^K9%/OH^rϭ/Hˇ(#X#%:pz+ XD/gAFbQG@ԥ*&EI3"AjHPVԴ#1QlZ5\HĊ# JӬ 4JV4ZZ5$ul&7Z%0QzT3iz2VajkG".er-)7GRCbAD6W*#s!YB WЭT`6U8XP ܙ3?R=.ŎD,PhԩD!lBJKڒFASY:eujDD`bJJ^ִp'ָ+z+\v%Bkԓ$1Kfj[$٭GieAT+TVsѤ5'&DjqZWI\e W/2;фl /^+*bԪB3KЋ@,lgf1EJy<)%D^iJuz%{=yJU׆>5RV,-b )^VΉA+3ALn -htz]S^0tJ=hm;8SM**Z%+:i^E(ΉiMTP謔}Qie)V*Ǵ=9aԅ,ث%,- LTWȩ1^T ;\&rgp"Z”7;DN3D]7!K:KB,*,atܦČ*h,ʬͪ҉Bβbg@_k8fgtDW"k9H44ד"2kG5bTA4*4/A$$6Fst:I4uTffwPbH3 -*ДsU_JfG\d96^*V,A(c,%- ZrQ3U@dnVBZ]^K }c^qX" ͌ѳtͼ9̡ +_Pp?s.z\jdE-MaY)ʤ'tI:U!Uv[UC$J+;Vx](B[@4AD Ѵr免Y|h6OIIѦj,5Aa>rNTBiy5lo<4bO5k]lo Bbm.ͦHI%J@BޣZR3jҴxYFUl0^\5FsT,3N"lwXUN˓O gҷžvӥSc_KH7xJIIg:aFZU&BULR{>0s|&I8:U]Y^52#uhJV9ȟi\MAM{:X6j\k3j*iEWD1b*8ϵܞUΞΰ}8Wf Nbp2};X<͹D]BlXLDOJ B`η ZfzX0j$41F%$S2Rk46ri} PPj qisٵMXYnEu0,03}HG EXiTYeWD>rP2ՆíZ QCfVx[dߊogF9}#&õV[~r]=1\R!!ѼܠCYihY)1MldsF6jC:Z*4z.y J֤N7}1M[ʃCV(rz$epT.& 7JQ FX,ԙ E5*VTVʾm.k:3z.b-fy4>|͢x*957 is/BEV8GYΘ46RVb KfklY45FݧQܹv(ޔqD],HBoFFLZD]nSQEW'ԵWe9htî&[? l+L2\٭nKYs_I/4dp)0)%RyW2Toe$v*cf$ܯEOijԱT)ڭHZ=&~q-PjriSTr@އ-[4s9si< Ye6ld L2T4;0@1:;f2!ZM͵p\m;=Kw`=8q'$,MfnhI&2 ȏQ0)=-J3P[dLS)jzVQVٗ+Vtk#C[fzA"E6g[cD+.^KoY~VIH14iĵ$%Z.FCeS'=eZvmU3SQ4`x-!o{` va]wh,/EkbͨBhSQPŠZVNW霧6kA:NͲYo D"ƞHn9F}uӶ {py:RVUAJ,"jAYXijF{3-UEƭZ<<ޣR:4\jKqKc2sZZiƾàTgRY\Jl~h=bf AuW55(Qg*r+4W:pɬi)06cػv[+y:ڲƻQu3NWHmXɭĚd߳JBgY_9T2q(JPsתѕRkCnBέ^l$k{=k-󻸉׏G:eVrZ,ZȪZJ((1L5{%.ďbjJR!9,D5z K D'w@yʰ#kyzad[lt~(Ry\* <2UbhLXw-W]d =".<:ˍUZԭm+͑mususl-;?}'1BbVWHdQ|Go5$d=6W*tHrҌ*.M:@嶅0;e`5i]Bks?R 'swX޲BƎ̈́]3z o8SLpQf.G8ǨQz%?5ɇY6:B/S0ኦ3=;1)x_W 0QbM`4qֳNZ--R"}F^hi7fs/IQ ЧN*ItgE lo5Z6ȠBϰY; ':+3>ERSV*%eBR1 +\me+Ge[32 TXÒh M.^O)K1j&e&heR6jbHB0+K5tlD²eC3z Ȑ3Fwg:Xe4mcJc]5rWfM*ѝeM7ԸEeAfʵC2 K-M+ԵKS38g[ɛ/:HhF>Ӫ6f.CйM/JZp;kKR6_UUj.+mf&<彛/U+>,Ta>Z ,2ͨ2ZI(_m8:u1j@_S#Nnlzf"qik]EQ\:VQ QEඖ#(W=rqTq4i- :nc⋎8cZש!Z*֦ilҳ#E. bXgiLd~!IгR+P"U ʩ$/f4ޗlzk*sӕsRf(*LCP嘚9gPa9S{E2f {UU|]ƏL@ZR6fk MFa3kWUujW&~swYoJn2=K`^Le|٤!CU`զ9"1HϜe֯BI# 4BU{PKm: QEǢB * @c`$>H*UfTallӮZĄ#:::ͭk3frT9F6w3)<ɋ@2:M5n1]*iJ>RB!*+Ybs.}OYh`GSSΟblqrT앋Nj^@o5HRAG{Qij9 Q(E*hEj~EGzF kY ]UC2%Hs@Ҧ#9RzoGpZfN,5pVקnQtzHYQlm ",Ve'ZW.N6r½f2ywYN5!I5UYB=*: Kpު-RG mVђc"h 7AAeR9G=5MRmc"҈nS9^6毮k"[Q,VkMM(t92R҆JQ]MD/ DIMftHL;y[9ڵ&_B5:h8,k4 =h-&Z2*uƣzwC5"qmmU]KڹjJejċb4wo=C5[Uu[55G=hUpƨ)1A}DtZmlIc0WAt3t2欣C!̭/k75Wi#D}U&28ET-R wCƆDPR\vU*Z1:ќo LPՂ5[4ΏLU(*lrS0[Y=DӚrdpJR,QN7Mf>s]I*bsԖ h iT .*B-4*jjXULS,m1릥/JXCOo'| #39]RbjbjbgH0!"#12$34A%B5&6C+KǣޏjvK+7mZ =F[Wp>6 PlbmG-{,8{GwI^GӳQwEn7y? ~#r_-T8^Q○ -=C -Jsb#pvKr1cޅـ/W")5fJX&LDZh6Y*iN*iw.$MmhyYF av9p_IʷD9Y#yg`Z3FiF^.nBk,e`q׊c4RnL[o$QD[D6h ij[,9Jm,<6ʺqTD|5FA 0*#Jf*;Jʯq@,H#}i%Wu2j,g@,2$lC9!i-㽸tY Kk&IJ"qpտDd;eK`bͪЖ#E[yEe[綏ܤ ܫ$d:SƫQmk{{mɈyAjN` QeSI"\̂]Fr۵}d'GTi()\36't2HgcFcFbeJXG IEEEXROY:@ne"JsyBrAlrT1K&MX32grEE.7&InnzO`- QBҊ)< $)Ew*PIbsӺFnev;<4Jʒ)Dg"KeJ1#ʸ-$w;⌰!-UFY#/tX &qws t3S}Y‘I%⧺REX{kx"CK1' YZkih@"x&L(' D2KT6OY{C'(-Viw;R%-Q>eۦb#_+Z4]m+F@IsLSS4LkӪLGoMG]-JUVAҪ7Q4" nk=)Ֆdb^W"9+Z]v0Y fqJkG Be4ݵR.&I\BOQKO%Yncs,,@_oXKAs7&Yc) dN; 143 S#^`Nhݶ0V*9᫥ʲ8)hDhJ´mFٴ){Iˊ)҈^ğ 15="mG .\K'ҝ'[Թ4>S~rM9m,r_ntjˑ?bI kRS*K0 ~)ر"HZY4e2LjrGSCMɶ1JU 0Xt=Hm-˥]SGZ]yR}2W$BŰOԘv}ooI:#K&C]c>*=Ud"L/=[a %9TqCԶJ6TمoUUd-5>0::+ ƥz6m’R0 cƱP"M VUɒQ3k08٦Y,X.no:Ň oq}n/Rp,)Lʃ9֘o]f9 j1{Q5VJT( @!(N?kKTB}V@G)ȭ)!d%قح\ 3Vv\HrEiV"sY6cYD pWaVʡg'>HMk+,4Rklu4&phH>ifVE.64%4&"R^*ى25*fE .H.)]ku+ W\lXh֕#jnj2 #S7pLaZߢ6"o.uGvM7rvGDŽ^BSJ]Բ  Zc#1:#jny-oNY_M}9j+ȍ~5 /Vx(9jz;񥍼KZ--t_ȊqUirY~G$hr\2'ZNj^^rƫ̢姖L$_{ 8IvLK/L(P*]N=T-f x̂߱ӄnQ],d9}SW-Ru=چVΪ lZ4ĕ01!4%ڣxVp6m> DolBыj#;Z0׾7r# }OǪKԩ7xr˻o= )OQSseb-Qjr :50k #2.B*]mt4RFs{c Ż4"g}WriF XFt)1d-GW݈(\w:?G N+]d$,sŲ[O0OSbb˻;?%`Ke~+#a&hAk N!!YQ\V󖇏=5)QATaZ}0K(-B[eyh)PHeI5 "f=Lڲ/ۼck 5uqz -VfT&e%QDU/-UHƈmHc쉣cX'*C*O='Jg)@LTb)u[>? $H&U񽕅"㠩Ym!G:^j-AnB!KZW`ox|.a:m lKofA6ltY k=ҋ!A:'a$_j2y<>_$rtr.o +PUה,4U٘ ]R05[fz٢4,ݑ\maki1?Ķo1)"IDl,kñXw'Ojin8m8IYZd0 >lE-ZWj5{e^lbYحq6x#yKfN,YCGcj^FR}–욝&pٕJϗQ)EyGmQXAVi ˬ)ApjqlE"lRe,Vd=GW}0m5D1}$}ܧ)K 2Zxw %mGb܅r/-]VЗhr0כJY/CLd3(.Kl[ZPqY4^0v4dԌiΠw)S Go j Ai (و(|Fy 9@W3?^.aI43Dlv"`G@rfy7y!M}}2|AhޭylA宫.My4ˌp>|Ƽh\B ׯ2S^LBj7sוp)nEy7]oyBX_)Ȼphݵ ڶEoPj]4\E)k?toP$P#$WRN쌿x_Qf†]E#s,jlP u1"6RUBZu.Ȩ$unL]y!HƿeQiUSt صxa#2I]6 Z@䐂02j$LIwk&4H誳ƙ2ީ:HeB:,$9nv)=nɆz͂oi ^d4hպR m+[K1/)er͏% ݗ$oԱk5;VKdg3& ?0IůL'pMLڣ33\gN~ >69`ia%Խ[@z[f'u˫+"Ol~t, (!ŲRPC-3pa *+Q\[9@ OэjKvJ6:$&^q+3SZpCny:7ZZ:-z 5UKĤZ{`wڠ` hv\G1ߑ¬FH"7DG;|X}"&&#&ΓmT}F.2w3\ ,Nh=e K׮cZyP 'EX\HIIȀ *`Z/O tUzG* mco9q,2p'< Un.@LLJIyud{2ܴB^DY5XyΖ)}l/VEi}I<{"F#>OzfUW~TH#D5˦Y[yXRIy}]}kW6q uP{2|tf'K3qn8tzȕk˞J7aP"7.~f` {!K/5()ms³;мlI=t4|]ggl! hIAd M%/o2}Iw$3ȢY Ԃū/ ΦVPe a%8o?fYe"~ˈVdu,/:Ea\*JE8-̻Q\PGP} [׾Yݘ#S#zENK m"6VՐ J^vΛ'mNvg9l5f)ygz ͨ{477= <em$~h1VfA}hj+yXa;&Q9t7o_Ħ.6aٍ=KZliLwHK6e]KI\,ol_SrJњ2d~NʵPYTU =38NIĈ"hcIFfR]ɘ$B$B*aa+1r1%8O+*J-rDJJ54h ,Va [+*E!:euP$CMSqM djó=C hɮ !@S ^,MM$jHE4mаV??YM/|=HV8A늲su GTck8De+*It4pu疂&*4ވ>;KHf,V2I)i9\im7q$֥) 1V7 Q}FJum7^m+Dcx1d Rd '}oݓSrHi³3Q,$(R%4pv<0f2$e]!PDqG;tHd‚\:$Q<㪄4m\QW܂Z kB>V[F+6VD2T*>_ӋoS\0dkKb~GKa4Mt_$J2*(Dr#R&;J#6V%1D$Ԭpqf(6*#YwK+U*릴a@(ԭcDR@/`Ė.#JhUu*LknX''YXۋO踰;#!Kx4a[η:}m$IZbHDecڭ>x(M kG Ph ;Z)au?ek[HCҬ6p 4n+ͺS]׸]_\׺^aov<r]䮩(_)y>x9N^|s7`^MpL܃.HBL"/*c[K乏REP&pHB^4% 3 o%HqGj,$Fִ/IZ \nA,4Qk@h)ZvWRB&01KeaR L6vM- obNqnX,BT^*Ѡ{ \Xێ+t=6,$\!0=1GsiԖԴI^eΏ]F)R;T( $Lj7  z*}ulI!jE`iԲJ2jA!_ \Kߝ8-ZW1F4u+Z?ˌ_[R&:R}]V\i#5R}p=-ˌ$23VNU'Į; b9:P(G䔯YF(`@dB% xzR70bQ] TJ L,_ nczox i[Y~bm1šs9QɌq/ϼlũA7 :E}Go^wjUH3tV))ENDIjU"dwTTFƫKZdގ@˖$")D\+D$GݳZ]enRD-j(d$F iPSL.۶È h*DŎɓi' dlfG%H mr(IzHc,*]sҶlO!ѰKj;䶕}DW5E GN]TF"bl2hYD›kZ FET 6J[MƘT1\qoyq$IJ;j(ae)"9~K&@`Tx%lKq^6,u"gHXTՋKCj%+el3H+ x2?)H㨯yYRS+/c#jJofdVI:Ekhp':C-U~cʄu-XLZicB 5Y/P!wE,!:⨭B-֤F,]cQݥwK]9S4KMrRIQ,Z$${$qH`YzW{rn# 9 :Ħ5Jf 9mb.'~TBixӲ Em,U44>+GȭKlAF&Pi¼v'! JQ=d{ $y;vO;ILkwqDeJiPPTxrYfK[yT A.LVɫ (,ld Dv O%vi[ Fo-,G\@ahS,KwƣFCy=LhյL5dI*F-\GxH]QvC8l:TZ^I&[!E_ 럗sA0)hHMz^ʒD7=ʻl bO/dXTH2AFƼc>/taE MM^u'h̦5 léW`ycWd!A"iievuiv|u1Tap#I~`$`W.Q,HE /29`ܐ_j7mO?ē 2$ Tl )Pjjt#A, !01AQ"@2aRBPbq?̙>FHucGC%duk7Ƹ.g5d223'+~}ɺ*;g!} *|rh'uI͑ĭv'Cu_iՌ(t;2h7bd7AIIII:potݤݤݤp87i7i7in= |{|O0`qz}=O=>{ nt|=鎍Ï#?雿ntpIBb uǓFIGLF9ٺ}õu;11r.B4Žt;bx՛c3dV "]GTՒO EIiUCz}Wɻnߨߨ߫ rD|}# MM'Tǹ${5qtt87r923p7~~ƘDi|O'уfF6mAU@ήXR)4&YV!'B1d&*ꮒ{V#d1qȉB<*뚫&ȷTXEqj1jl#WdR)ٵ)6; ѪM14v:5d].({BHE٫1*L*]1ܬTwV.5 CW1c@.~R-ǂ X5Uc၌o8:UIx"n$j7`@]I$ֺ8 qwt(JX·kg$`vGtG7`1H7EXȹ z$qݤzLٻOvnL0(cؠI3>b2dɓFٷ!ADЧЭDY>ϻrmtU(0->D! .\qg$SrfdR$EXL\(BQ ''ݐM"`/| ͓҃Y]fV!ٞ&FDEb&<>: W"VDMb"T;3ll|!4fIPb,ut&Lٴ+J:3bt:օ?vٟ99L>^OL=FF77jT)|ꎍڽW23w1G720cѪpGGBb*b&6*+&5tGV:OTͼh1ڢDt2k?-2>_$ s]:p:*3pb" 'X6!sp+utHv-w#7nnSZՏ$c&F1 *MUG@ <HA"u! '/H2FSIR^gqHRO.|F3UD} 7ЪA4}vx1hXk,cF34htBc8M>9Qb#Sо'/OLqD|rk9zyx:>E<#ukYRh4*2)'5r>cˍQ4hѪx,<Gk3Y M9YlthРB?hhKd[%XZO#>DVk$}> I"IȿD/BB6lٳbtٱɿci F6G_'NqxS=$}c9 Y?Id'>t;`22H='RxIYLU7ftdz,c>2Ǔ=-yzGD11ѡxBx}c;!vb1?'E&B -T!_Qich3:FB# 1# 8TTUh(D"#C=a$a$M')#xB]3$er=춌XL%Uc`=#z"[b&F"<)1g/lؤSٳl,ٳcf#r9}~~6#cA$SY#*!SBtKr)7Y@Y7M7t$N-d1Qǽˡ q'81h8,H$2atGDV>D 3x}J职}>q'.F1"PGX.5T(4hucq$%+(p: uxhѣTѣCOē.4."."(*4/ ` bUՎb$*kF ! :` S<.= )=/ czNE~ɢUyRH11t2GʍM|#dc8U #3xYRH龦:2f jHcEˋ仑"6o1 hN? aqqqqqqt$GM([u*hѬZteuBdMˋ>D!B V<"I D!@BŌtX<ˆ2ucUtuBE *1 c<4t߆B-K[_9rc:nFJ!1"AQa2q#3BRb4r5$s%CDcSt?V sI ,qa iA^?=boZeKՔ%G\-ݠUw^K׽cjr= ~ZBv~Ƌ;f_ZOj[Q@Ci!{A5h{D w_ZE'iMQ ۶GjG~ߵ-ߤZ>N9jWZҵPg\,-PO_V>.kQ'նߤXZxTi}bR1k[o҆ﰈђabߺ Հ;VWPv28d dl-<>kO:\Gde3d#k CD23KǻrlɮV DhcP,:|41q@NaW]K:ztm!L2ۆPkDˆ~jlj|(<11k7 nGZ4$ `}k[ %=0KR]" \2 UxO6Tp`JÛ㐭JCC Z5ilv8kA%ݙVd#T淉@;,n-07gcO,L&Ε" yrimIcp~ZpRpm(D\x0Ɨ3Dw:U-Ng l֠?AXq=pS\*3{6SĖknVzh| #kSh[E3,Ln: g9¹ݮ'd8k\G]:CF8t̩Hm湘6\NEwZ0U3g3)y8 ¥֏,;gzsD| -kLi 9JۧcNKrvD) 8m*j@Ӫ76I?!cY׺$k8rOمNu^N< 5! ,: fP㡻ϲ09bd2 |\Z%qUa\!VOH5BP赆G LsĻeY\I \h Y[nso9jz1h;XyJސ K8B7 xr"g#x |4ao=ǢܺEai 򆅿Vit \U6Ty5 [QƮzOhŗtOFxqBƸ4/q4jqPrPrڠN?%2N.#D Jg]aq~cw(]2[KڰyBCNY ; o;< qB7TǂM:"ԓwL zu;MX8 CK `]<㊺-y)7%7ʛ[N\Z  .olj$F‹ K>[#ݍ$;w5rMa u5FjGӖD ]kA B 59[<LsBsyCN?5ZOq(DK↼e-YФԀyFϢ»r: I?''p >WTFm{^(XTy '. -F}  JިT7DޙVj= W||I9@p`Îx↑ĉ+x@ R};u zۥEiX5啵'3E#we\yܦZO^n{ԋgɸXK&.A} Z8{26ը[{Vw$e@o&T]meU$ne@S/ =I@pu:y*ǀP:9ŨZo\9RsTqjNHZU7Vj\tQ!wnYϜ)sm+$Ua%zm6y-%T#6B Kgq&8#?E9ߏn}/Sz1U}JhѻQYe`Ec')~UGv(ߎ) aE9F"bUTXs 8F$s[#@wCyR<- HEsk|͠qNA8ڟ$ESk8a[Z8h(֯\5N$!Lt*{:%w^V)$W5O'' wg% nyk\x46y]X;{ G~>̕OP ;Ei@z6хNl z;#4hN d7@OHN=T8\/T)@{b G U7. v~H ekLj˷LJُuW2X]T74 :̟Gy"=R7tB;z%#W*Ք7b~XQ9NnЗ+<ͨ E{ӪinHZVA; һk#{6ӻ]죁OSTHãD\uW3S47 nif/Mx&[ XLL'0_|+Sk"iBqmTS, (u$(EfA? uͧ<1YM3S7fhXpU6q$ "4 tM!o!f3$̢y?[۠񕅎JpZNŭs6H{'Ok{j[S4G5R" '{]-%Ҹl'ipg`- {s䨸wtO-=m\Ms̀: Fsꖁ #%ڌ.K0= ?V%'p$s\:D(i+ oLaChZ|1߉ -9s^s?4h #oWWuk,h>EgPsn8|ֳRl7/]t=pBjHU|9Hil+\F@xjW9Xī@u]=$+-MouaSP4[O1Im&7E`pBf;N.[;7D&@>=;;^x@ v7sNMp*NO wdo'쯪cc}VwW<5#퐽*JewUCCª}jڇz1m|̖7_%H?x5c!xS󢱶R#$X_X vDrݓ*{ _WُelU?~d?F?󐟣~TٯG^Fvon(S {Tq!;U-%6vZ`N.k. y-<T%wLiWOh#xp惥4?fV.1>YQi3Cd*LQ,`sYf>kk"{*70N TPw-Lo7@]JAqUSۡwP:;' y-3fm>)0nvU'5舐x ">eM꛺'T 0A'tğЃ#)jr0Y˂90F:fZ%vä3Y0dwyt_GWT˲zX.kOHiE}ܫ:+'MnW.Q䃋Y4ܦוd5Fh1r7N$ڍ1-wN!?uj_еhD(cXd,w "f9[0CuSX[g)Hq!wy"g@BJ6+Df{OR؏]XeBV/pFGj9=wVDw#k'Wބ惼b-o$4t8Hz@Or;MU*ǃahxMDzWϽ(|T28u!CKjP˂mXctB5Bȟz(z-43k-os J=]Mm"O0SL%;B =㕴b7JôۆN<<jفل kWH[4jUKǚt[;cg+մpSN2ѭ{Tn@C\<=IP3siy+;wԞ>!bTM[fз0e AϮHZQ=m^@Ž8OhqooM76nxBXxbT>k?yis yI>!1Φ!3<% .rGYt}ow0X<ߧ30q!=ժAjKHN=\~wj<| Xi/]DSu ]4hOPMjg}M[ ޅ8-\x`\>:I>[G>E.>cb+si4׺ccuuO*iAID ܻ{> XT I[M7Uٺ5:WzV4 zt;sb lU;U6U7/_S"Z~d)a=)MI(ǻd\6Z1}!Q[#+;+>KT~}@orVv`~C3ez>GpfB[Œj6 viBWWmퟷދ{@?hqUb#:8^SzӴ+fԠ}#@kev¥uKy(;s9[sXBc|pA-ʩ;:4sb?qu ;J|v ςmsZe*sCZH?5b$i&8-5 (MivhWp9zOlg6~)CqP4HԤ Yǒ3&Uļt),#W|< LgCІ 5憑( _d3vw9SjW=h2kc7Ө)KPKg wju +q6ud!OMbrZ$ j^0F7JkvKǐ&4ƸĖkE}؆7-8+enx.ϝ=|_8zl+y?5IʒͭN0NEA  m<\|?C$%{,nJO }B1J5uASfMſh+M@DL' ve4U|YkK!<Ƌ*dp ⤹4;Bpcs\\r(G+hB>M jS.lNqpC8dv, cE4CL H&OEĽteXax‚a= OˀFQgq.渘"`޽ !.%Lb8ܪ6]>H^NKt70x s}`E(Km땫GѤ|fSFiaV8nM#-PSgڳJg;?%[cUb!A޳wRn忨[Ik;JN%Cg5{gon:eÊm)m6̫ȅpq΋w滮޴y#6ϒv|׭'n{N [D7NutgКʛy]Gv3F3Bi$|RȯkWZބ\>Sx[~ zE8FZ-xySA=Q-sJ.u]Bh- ll'f2(NT DJdg;c=Bd :ϊ]n6j=^^(A|} S00hUkF9'<4v1VvC/?}z15)Yzf>0RG|wjz.~چ[?l-ڂ|Wg٘)o@a{?4 ӏ5P,yazϊeaĬ+4=PFQ2dPyVZ6MWLt+@ϒ0:#yIhA֊sҨZxj8USy*\e*w>§ϴ0 5.5˃P ;+7Ѵ*TޞG2>ͥFM?F?ϱ?.6ܛ,~UdAV> r@d_O~UeKUAE۳y1?~<侥*/PW~Mxt1| <н}⮾H}tK5_^:?n2Ÿ1޿{R{{rӔ״w~~I}lE QP~+?MlczGYTD+mJ@S'xXU1HE1tԓ)*gsU*qy*yG '*;1vE؋SU-mh L#A#{'’f8 TnT>K0)N%;"I> t3DC&#[cX'^WM*SD#B]*VT&DEWGAZIdEScV7jx7=W`-E}2]Fe(K˄̵^:x q3pWdu(*z>ϧ<$Bs yEPy,wSDÈ3w(: n(˚ޗ_\}%wX靤NUP0juˇ(nws=Watwz9ʎx}Zk(n>tD&rDMw AUac\tR][R g$S~tNO6ꩆqM'UP=};5:tOuu0~%-5Pĭ|nD x+Z(>1xSK`!{Otmhmwk| -y_.kj~J0T-$فkpsGOC `A?w`ϒ桧9قMx9wf1i=Ň.&q`h0VooeJ6*PmOP`i"v9El-dof2쮜[Ld!q5ƎRw2ϫk[Hrti!oXyan5!w/n<rKځ J/d[2rKo>]:)T殽Mi_ϢB|PA+@ңr2fnSnȦD9"άцJur-h?IR*zyLbv 9]lys!Mi\&kh[.=?4?Uh+8Zr0n ǒu0EL4[&j;у E`zgUU>z)~Zh+gYQ5o 7!m 8^!s6Z?S]u4]2w%{l㸫Pe-*R-/m9KVj<}=z GX<*;wP` yJ6_va_^[+sko +5r.һmL!SM&!j;] J CwQ!18ʫU`}%+Qd0IgZ+ByA%88|!{R Q㚜w'[Pw=%ov[]ŧAp[C(Uv?jx0pڪ4EWG]! Gjy[)A~Zglyջ^Zm`_ZmΈY;>SYHY!}lc+ _Xo辴㳵}m T/H^B>׮#LY#4pei)ߪS0 ? T~Sٿi/h6%?Ө=wi+C 󹀻gzP[]ׂ9 Xi(_ݏ$@hb? \ݖs&S<+j酡 `NgUr<¨kPy #s #taW+a/UrZE98 IΒHY#ˉOz7Bܤ^EŶ*MR}jT|bw|qo\t:\3fgS'[- x4僞Fp E~Ku2)Z m%?%ǚ̺|t T}:M/zHjH!S ŀռea:tV<PFt H? xy,|℄'UqXX9f3<ٍr:p"HG:NW7 H#i#ҿT<@.&<%iw[ 57xNo4@]f5Sp |jIA ? WuXcD\PQڶ3ڛ; YEG%!KYprYdXh>hbqG';2 @nj:aVٙL!יMury ۞z#OԨMMNhnjl}\ {#]+~g+aׂ3EY * "t8 ˥S*!GFoXP{,9{tyL8ʚG%SiuZUN Yū )U;E]T:xҸArK6!lSv%ճm6(uB0r S܌Gq4ݐZhI<.FGh%36k䙌NaUGTј-28sLe}e7Ԃ wYwCiH涉=2ɇEV*ͩ0ׇ;if/%:ɝD/WD }ŧE|snQ!Vˆ}s㤄[/K|j뼑!\+T CUm&^.=ΉC*[jqAh$#65}S2wez;xo4ws#<8 ųw\|7Tp' [O}N{)1=% BjsMw''~(QTGU$1 U@c>)'+& h_̐ Hiu[UBבA82v# po0pY܊i6^GptUºxe{~$,IL+hoW&hYAOˑQ=u:/G'MtVߒ#4.bNhN~ZOGqeO[D-lqkS}}oUڛWvV۶8P6ݳ(vӎlrߴF9Kj#ZH?TT(JeEO:0~g46r,(}'PTyzҟg_豷~&i?m yڿxQݛ侻y/ (ϼ Y~[dQ.2eڄGi8piV!o:%M( q}e;#96UcM)f.[K[$\7⶛w¥h':2cwpZsD-"aڬls⹨.ePs]PqGR.Q,;`tdj|y=x,%q]6Q'nϚ$6I uz/X?5|;S{$ZS]q]ϲm/V>A] lh^:>FٵOcLvY##^aIKymrAܨM<GR͇DPxnR3VwEΥ.ĘM!,45!ꅮL.41di:2NmVxJ6*}"Yv&3$.r ӟ5 WWpתr Ie9ضZ9Of^<s۴T۠&]#z^ 9KntnWޝu:FH#ϕ⡻8۰e_e-p[s#Ш$AU1_GcA4OO,+tNtV)>T7*)Wx7LކF)PptK07HmcGd3nGvCXߚ0Ќ|{NKK{1@;S%f@v]Q֕iq@:zeoO˰eLM(A-#*Yvț'qC07 D̅v q%@l 緢s90I}'4cD.nxQuO9kE ߹Zc";8YYaFHPaͯi99-wUΤׄ0P`q˂ޡC\z-kxLrB D) 2=(n*"q ceo o$qEO%2|[yWvNRSςGk|CDey",7q<Әg{)p\w4CGka :⎋8+H^(ja63b L[qR{v"Uy wNkT[^)8 )AB\ZUr]ܨ/f1EG tds@r^@ 轔eKu"HTp\ۗv$8dDִ Πs  6rNHRYu='v+]E͎ˊ*8)sw.ճeNVw#CzTRpt7m_1 ue@Pr06P.vVJ#(,⍎yM-ꍤ54L*WsR [7䀶I⮏L#'wsy ZVZGk5ygsGT1{|t<j<ȃXhv v{ђf!dʀeCDqj7Ɏh)'uXCZJnnwq䁴J ʿtTiP~(\seǤVym|m7+L-A<淚-BY381ָsFK| wQw<|/.V87L.h>٥e0!Ҩ'+veDtki^ U(#ޠx#V閠^d)ÁWzpR]+/t{OU 5eNj.=~z"CK%ď\|"wyMk x,ln]Ui:Qk]66뒮ftCzFɷSl!%L䌽eweP%%!1AQaq?!Ջ1UT^vփ,vf|Ђ7B)N jYw kM`-~Py\9w*e^I&RZSḇ!m٢'1֍5j a[ @Xh`h-.~ni^4{s_pȯ kapt$Yx=DLmݳ"A0ȧb{-B|&)-T?Ⱥľє9 2Cn|G+^)&Xw:^ r(Jn#ge _S}j`۵KX8"eĵcPzҰwTzH+GTAʨLvAZ@sOpG[fSyX((b zyB¢;CrZ=0̹  JU5Ѐ[+b̊rR621M 7Vgd&Z%oqcLq!x GO2$Jp hAFFX`%4<EV±4_mK.:g$*|qqWqKrT `pm2RZk8F)w<i<9e<]elrDaSވ&^S+HW+=3PL?5ۆw 7F~oXڽD6gYB)CvbfxPc-7]K=*.ʎQ oJ50ǢTG2: `0c!Mdnt^T,3F"s8dm+詿SM%Z͙6N9< ȵH0h/Ur`F)3%6\P:JfѸDnٱm[hPNWQ߃Bu"p m`Pq6R{Mu+W sD<?wB6 JɒI ;> ])=-<%d_-FӠ8=Bn G?6jn&@,Hvaΰ[=+.ETjJŒ&jbej,q)Vmx.:9KMbZ κ~svcg.`vOܾݴX=x8]¸pYZYYabɄp90ZrWnR&N ߸ǜ22xYrdg0uiS59R.U3:H3sTfgrPV^NkZ\['^XSWmdɈm`ex;bF,׽RUs@# g)׹A|ca")h'袞e[#} Lj.p K`++"˸aQVJ[RNa=|*3Ugy[5%IӲPիwkXhdC%*E9P읿SA"۪B5F+EBDOnm3_Q)XuqXٕsF{ 7JBD& kFHjg—n- n ͬ;QUԠ#`_1R'~95ܦm-T:¯|qӺ[KM<T3nW41%X#llʼGJ+IY`9C.!2)~&@@Hq_Tŋ C10?"3[@c}ˍ_~f4ûq2Lޯ,_YA?i]" ⭮jMa0Jp@[xTU h_1,G1Z\xyԗXkW F+vmB ޡS%* oIH'a v̚s&_Ȼ۳g/dp+C*_Rցr&}U:%}Reu:昣B  @ evİ|4̩CP1O`O5r.hю FbA|7~ c,ƚ7-;T>,ƽ(N,s.:3^5n/C̽GROrxQnުsf%AdnjsgagћN3bիw,e3' Nm58c<ξ>pUr,{e #EU yY(f@0͎,ߦ`f00ArZdl],[lTĨsۺ\>NLT [q̩lnu (hk\a2FͩɎG`SI&a `(֐o2Z5< z[ 26Z@KdQ [v1D ; C: PLky}L\c򦣛l9S vAH<^giP(9gj@4@n.äu:U ȱy_(%X?U|lhL4L5pSP‹~x9w D*|^51Bf-vU_Tg. {e"0 L C>m_0JAܗ%VVEA{<8[0Zࡎ!Q_QƮ/._ O TPečH4y2cL!6#4APT.E lً}i/c9S%b_Tbt(Kfb*Nۨbʽ?0MY˹iq.p6Nwɕψe[Scd@X-|솸^,DTx¿P,V00eA @E uhXư+G脮x|goGX#4YsoKUq!qA#_YvA|gz"*FeזSqh{ ѭ*-SS}TCThAXš1XƂϫ>^rAgk(?)v f)sqI #" ELQU# KUSn3HN\*R.Ax+ z_qp:<1!HvqiCaVfyB)bUt=ƨ t[*sw1qrcʏYz,BB6j)BA+~o -AUb(+(T=$b6^VEDCJ`FGKkBLڼEj!Axq#y_RNO'CR`#Wf?E9½N8Zٵe0%p1n ?0!/+!L̚P&$ wbg\2 >J ?UgB/hpꔩdإw1mQ*p?0R r9e34[ ^uG3 0 ,:Y`ۼ ʅ:YŸfl:@GBaPz |j]n Oቲ@#`HY#'Y\L|_B A|?R mz?@p1ogu,]wfkA76K9 Nypx z!9ޢW_t./KkR)"D*ga7pu%ּQLQ_Vfha+oy?]³<`kx ձBKXoDܳjy9yyVIf>Čږ67'%'ĴN7UӣN=|?RrѨEeZw'v8z& OT(>1:[O rtGYQj00Ĭ,د|F,׉`,=*.i@a;#Ֆ5`+FǨ7(.9g3 32IU>+", BVױ];T\Z}+b{ l dfX/VRg⧯3p˴nB6.CP /A]B)m> fup+DB娶h\U@"2TlFoC3t_^.g bԣEŕU~vU|nk)iQo*^?f{핑kK{k4n5~e9NxSm4㉰\爰)[n(0Fzs h2iwqP?a:/%.QU.qSP dx@S,'Exʟl/jd #)-NN=^?XpiNɞkSk Co!Fv \ a`/{iun?%F$Kz 8J+tǘ*}*}1"G"\bpUQS,+.#GCt,6\z=:yِ.,kaXmYz%Jj@ -CR^l;s<\*\{"+Ёu-@&n&9,ͅ4OPR}Uԯ 1Y*Ùw^5z tN"}/Pb-6003W4ôy;(_ b ^,ӫx˖t?qrj FyaM 0u%l:j@2}J r*]$GRȝӆG'TsOyV~Ppp=5)P=5gε  5Q0Z?)1+Z04 ֝Vіv0-Kz\=ײΥLZI0$Xj v Lustq$ C*.c@8 FuDDٷ̼a2AI¸-cv8R_!ÜMl})֞H<5,“x՛쩀+[GJ(F`h [ΖV ሡjʓ2Cuu`qw[XnYe"3%F4w]QNژ5 mU/\8ZrH02RqYQ"%6|PE 5ݟ7b1)-vpBer…~ubRmKQ0 =6.eF1Qq=e`rݖQsp*O7S+=y8O]L{\l2d/KReWfDaQq3 ghd8Q-(s.bW_ WwWL%X>a_t\`g1DQ9SO4 fѕQ{_f̨ҽjʰƹ̳Fec&Z2>i bw|V`T3E#Ϲxү<wq>ƨa$6A+/W׸ O d<1V}3-=޺ i9s%'ƪ<).yGn[&~yaô;}0J‡6UbIxj\Qv8R5j:d9ޖv"^iU $0ƈ[-_vcZ&k_lX}zQwx[f3>򐼹P)QJ Dx#zpfT' ]P~/ӵ{J/`bix` ?YG*b|r:3726md&9?wlW{` ~% ['%?S3n P}2suUr&$*(s3Ep{K(9,h,%֜/LŝYč1gFi Q(;dޘı6apCXu bCZMՓ=-_ KcE@+)ѦNHTdP ]j0ee4cW*Gg*1tJ̮"]M^ZXO\/K{fcg#394lKj&Ŭ5HϬtp$~bL AeR"g3.n_yWQ 1vGW\%SEN9n#jc34@̺u=D2Iz7(4m9,7;âB7KX7N9#K~ka#`ysE%`^-[PK*s"[P) ^ӎ@hEؗ 4 BA~4`=EC̻LGQ}2Sp XpmA}b"D&VQbn=?ᖗ> J+ZMw+Fˈh c.gYU7QP^Х}GMV0&W\h ȼM.{< !-ĸ]fØ"4*J鐿 `S n IpJmIP`F)Dʬ>a>j:6RFz2{V4e5iĂ10.rs]p Gp>%)l&Y⿘ԕ]S-c4^Wd\Ri7u . 35>?1&i~)X*6_LwC'k7yxJz]Z~#]L(w]ooZ/XJHX:O`ZìB Eq,뮘]l+&M[64ieM5 <|<@:!DM"(iLH_p,mX <hBo"H0w䀜'`se(YUJ =Ja]'zwir@TɢSrt2yER1./Lӕ'?۔fk2ZZ!SVY!Q] 3% PeAZbTE"?Yzzpw?AxjPBvDZ뀄]opBɳJTO'AkF1pXKz5m s0*T9FjCCv=M+kDu,Y=~ȭOS%o0/^dPFjP7kn9eq?Zg?Լ9?Spq>c75> Ÿ+ӟʉ:9d>i*U>)u \o26Wu-w|!~g cΘr>`K p /AL=3`qekć,]@= ``dII^"%L3t{̯!BfYs/abYg7߹b.H+0͘oHn>[ML- bEe8Mo pQ"͌fC*=wpo QkX+H C#1b'.' .%P@Q '061՞I*mV.v5[-"Y@r]Kh5 Sq>X0\/LjVOccf $'5s)@S'GE(t<dS XX4m񎗆smu=#b1@,& =J hK٠Q 1(r( jpl' F;4װ @+YS_W$#> RjX*el?ɁUhFXN&rC72)ma Y]N_.JЏjj}gq'AЮ GT&=8Ka`.#_HvgP;Fކ#|-,E18Qю{+\ <)3Yε75-ۋJ -$0ƾbBV}LuAH( w|*6[xS"K씅u˽*ȵ} 22)ƖY rpȢ[ApfM\lp=pl)<̿zImRaYr op ĹG\Zwm V/8}@%d,lX#>e â͑!Xen08dZ%JtRLK/[MWԸrĭ5v`qT?H1h͏anj~&O~Pbj' ;&*R'3Z~%aBbHp oXQ}b-[}Q߱g%pAh T?;+ )|أ-I`]pD ݔ}m/<_O܏niU"|'upVyp5;T(%eU(g<ʭn*nv^Tmdqp U4b| cd;,N,YPIl/?av( zή]5KA9ys] TW10k ba'v\LZL}A$,ZĶ MZ~l-waef6IEi䦨TX> A_'EU lŌt#_`Om.htxʦnpR34hQSl/VW?@Xm!_fi3qrQ ]8~NKS b YzE/>aO9*,[1k0V&Z4BƖZ-ϩh9v)L%Js*-O<. )Ƹ)53@?ter1aYd-'yW=8,˫svQ@Ӷ'*DlUm 777N&,7ӅK¦rx5,hjU Nh?s{_q\m@P h SK 9$ko|I( f h[st5m>EsQH3 8U&>!R*x^龒[Kq~ YadA[VZea P2A!%Mav΋Ѹ~%gvq U 2Yiư2sN׶dx)J]`  s\jZB߀=[ҒjYx~JǷ%^ǶT" +fgvd`\~#f,Ό1ʱicauQF_s"Sk =Z¦y/ V\,qS,q>DHU?0킴+/%t.1)SQθ9. j #zWHNo+i&w KU@W \p,.%%J )j@S;3%1ޓ+[RD*@m^bNKy:P;U-0sKYA0y)qc)ZG0AX`/1F4CZCiGUNQܲ$bjbTsV7ZY-Y>eŅ-kb3le_W tF[L/L(`ДETLP⿿0 4Sgff([X0Vij\c߹A 7)~A+Ep1q9mbLUTdp0]Mpj&Qmm;?[2g6w-SK+5u*W\Լ{ \J=[mb/]g>b3&.2Eim 'k> t,ѧ~5أJEtnTvOYXyavQ2TwJmE( o  v:%jIJZyUMkbdḪj-G$Ⱦ6;<sUCIv6>2A ¤Ike*,ww#Yv7QB..e_d(Lg :ϤL Qgp!,6(2) E0Go(W&ȵJi؃{aZKѥ?ipY԰yJ]-_1(S }@6kӾg3Qr}GN?Ё3D_13w.?-V o?U+%mӰ)G.7οJ?lR Ynf 1VMP.;_8x|yg qy\R]ڑoc*ϵF4t6+Rx &BWS&Ha“[;ߢ*ШE"A:6_sp`p7 psXU_NǣAY?,+l-7 QᒲwHm;C,SK H-ΣBTJP0N2ܪc#Ktҗ!|qnjcLSK,TM;F?-ն=[SY^WM'P2# zalFa]9erVԲC:mdG%Kx4Ǧf5xqK Hj9&ro(b_KU|dDQ`Y[RtEFBey}PhgxI`c-J\\du)PXU X+Yz8NSH> Je%9w+5Yh7.J6_ `8>V?C.g,S/rQX=x<cj鬙Hqˍ\=[Ȋ/Cr1jX9?xBHe[FEs%ﯷzJR zK؛U, O {\fpOGglDަP !\,W3UGk2[?$W6z \/dggyAƣ\ZX>nXd ݨTJ Dx&j% |,{+Lej3W 8| |Kf[d%bT;?=KMLn]:Re8JR^?Z6?(]) IJ$¾eCf髶48A1ԈjARXd.Y.juOph`!80y+MK8O8Cyܗ3V(¿ pR8H9knhW'FIǨLxz&4BK`7'5> 3a|Wy(ĠYzԜ+%ߖ9O;3Yۅ>`,DnZt+u{p|Ǚ[c^"T%D6R[C2[Ҵlx]8 kkK\qX=4i51*bZ08X@iF=`ܼ͊U 0n<v8_K1n.þMcˮ,qt&(߮zHoG$q⺙Dl{Za9m:%CNzrיE6G9 8$5ETBmx G'r 9#|,$&{!Ĥ8AB1yF2:n 9 W,mB JT[D1 yXt pl2 0ig164瀁C#r p3u䀞nT\r\b5Kz/U[u1?fМNp7pY'(&5Y|v\T1FNU`&^b)lKRg஠Ԩ8qn.۹9jeSRӆa<,%bpys6Qf:9gnbc4q#6vTDwׅ~ Qb`jPv\}-lƈKJ=#.[r& L6o Nf>$L6?4PѢLPئ`Tb`R.;rQ2S-t!7WgZ6pHXN $|pG "'YX,LEtV; Ao,/4up{HTɩhH orNsjv3a7pX]$jc bгR"ۥbG9-0H8Qdʄ cU,y|3t4L\s]8Sq9"wv0&ŝ|AOl[%%|C>Ga9PYvFg6Q=UNj KLk:XtvQ<-k_.`{s)8繘Ѩ81qL-`rpi1 ❪0˛_Itw-h^;bCA:kDl̚D7$,~*Vjˡzy 0ehSc@qiM|$n9R3ecI1l8y@1Sl#kbu Ve?2,~&jw~nh1;`ދ%JL>T9kS -21[HY8`mpEss0\>H97>Q åeKnDBZJJTq9i:e-薺==u/1u]W-C'HwemY;X@U6PT6Fe)kŲ)<l@nE-qV5UiDjEgKQkB9(Rx;& ,̰ZHr!%hJx*h9N9bWqZ̧3,j 3@ƊNdτS/ U(hܲoJSJNШz0bb+]7dsdZ,H n4:po^Er\ExxJ|ݝz3#H-'(" v^! $+R7p-M1ꜝfhڷ8#9LpX(w s*EZs,\5|tl5mDŽL]pSN ;@ 7XN^Ɂ@!@[Դ]2W}d2s"(t0mԕ&l b|}&yZ Ig9ΓsR &BRu '%m> HMy,E9_ "._[x7)W>k8bV\ DlnJDDS[.?֭/n"\Oo֞'{2fsluars}51N<@ͤEߋ#TD YhOۢFi-c_JNgL2 Q^ $@1ҟcna _.{s9oHJP?^ u9]G3pA }K523V%yrr7TrEQ\4l-Qd4mPT{i "d3`L5-UߤAg %Jl5P"{2qrt XZy 4Fg?c~qvq~|̡f\K1XťC֕@BbÖj^2SO q]7Jn׋%plr:0ULI9"Ɵ$ܵ[AehV]M.59v$+S$Q!yh  vLt e]E!,lPZ ΃)t_|d٢ն55 8BSx6J=-!M+L R \-@4cea, @nX$ҁYsWCJ͐!@>_7<.InaL@҇{^x$Pуl5kEQ]9tdG|/eoO7,1r4` Ӗ`HtqU낃bsnGQ3N$3Bj}G>&^ G!|D&"!n}k/%`&Yu>kn^\?nQ DnrsĐ hܿ H@BT"|(.K`]xKF4bYBq C7S^D/\")p&qH6֙< gntC>H(7>^s%P bu^t"C65ϩEG7):/TF%꠴V'!Pچ *"cɆE'0-iW.&јK@2@mqeh7rIG; Z9XPyDy4Z"x{x _p ~PRHk?/5кq)+Ji}N0ߟ֥A21Si}d?4XaeƓIzev{n` ٿƙA9`nn`(!1AQ aq0?OSf똴ƥy(YKHFIhTT5 0t\=,v3tMp6Ylg'me}c ^M:m7S8}1z8s1'>3d<R<Q!X.cߌ`f􈗯lm5PEȯ)K_^Ȇ24SM5:l~:f( {Xcg{b^qZ({o <2Bm--8&*ӂqiHxT\>eѦ*^U O}ƖBL1ti!RB؝ؕۡ'pOXQl_0#ЏBCUl6ӂJQ yZK|Л}Z,  F"""4ēb"B$|/н1%hjۣ :wP"] ב$AIIDz#FbNi lc"\<4>ç!s.ey;5 k W(UB0|7["\yH=-mV$|J!hNuTc~0s6'Dsŏ9x> 4'%CBN8B+;腎h YZ$Dd(AD0j`Ƞfrņݍ wdDnѡ#dCĢ&D( xc|2 &Rz(I}M=HP)Xmcs5 pfb]apxhJܟ,U~P~qGYrlƞOEkg64EO䌺4AC_HSx4fj/,!㆚= coKiؓo?(vCO,ڿCWR-,P㟴/G'x=7A0etRӽ@(ˀ px kKGSEM.FfϠo(2IB#؛xx}?~̯䞧_W_hI+M-cA==#gМ/xbe/I92>3anVc]! Fr-C4Ƈ3Q [ ا "tHJ#DLj CF1$"+\C*#s{$A K64CfgL}_Q#HJs $15eE&7@B{/B1IGȈGDpx1"L> ߊCz<0 _CD:EODNb}WDo(Bgj>͑w*hi#aeyoYeyȰacFH*Rap|9 4*$Kd TVBJlWOgR_pUt!toq"p?cU,&tmpM©# qP4+E)V4$>4hHR=`#H7_E*/'DNoawPm G&= .0ƫ}owLh4ujCc^o0W([%EM?_1=7W~cR>?? x ض5ڳkIy ̉#mxNtx&|I =E YD%lp􍆏 qhhh+B(h1ExJeE6Dz;xH$r,_ h݋[$xj8AFҖ"HrEE*p[*\BlJbtIƣTl>Mo~b/^ǦȉCH~ilF W)g2]"NH?#R|a:%&Gh2Щ4&}e Â~VFmk[ZcwMZ"+oѳe2źCMm{bdAFbd{ލI#e^ȉ2N"^Vbn  0U]/̓Ǔ^OaG2?Кڿ"OPܣhO8kl\~e6ؤ- y/oeva6*PtX`!ڴ9G 5HDOk50$*x_/+:Q56Lc!56&8B9E;\(-IYƏ!%m)x7r}OKg$Ve`1#G]QsZǢꑏ)p8: ׼:M.O ҈Mlf>,񅗆!0PoE:O &7sn'ѷ4lNw=F".P Ql&ᧂ n#^~= %'E l6CV5F^a WkAS! "{-JU !J1F"'H#F7\~JQ$^0n $}~F1Bkb*ChlSyb2R>6&;? ŝ#d'%"I(:é_"b!YJDؖ"?"h%__L++OLHN12 Gѡ0!>'~ —3TD4D²2$K ALQ2 7b"<*$44B/PlƑ I4b|z‹?Л}QP@eme{?#?# wH ExuG=QWǨD0ҞD&.(!h6\|aJ͛7Nr(ѡ.b(DRB)W &bIi1zBAP*] q 4<5lLa5p8V&Q2|6zD-^),Rqn`~* ]?fpTH8oƄoҽaDpm!6oi6Q "N2HD@ G"LJBj R L<pAQG(Fűx7ʆ:lM$0_xbThQ6٠[dXLi2_ ± PD"DzpؐbS މ ] ,; ĸ ~(J5^-7CXes>8A"\AQ5DRS FHl)2Xˊ dOL CDi[b@оUVǧC/b 2' }Gg 2ch 44 I'B0P$*p_@۰Ѧ72Q(P틧 ZexM.aޅ"BQAdKS 47 pOz6!З08wI4%ta=~ S xʰ-a)؄GV B50^ @> bdec8HeFZO/eńCl <.{a![61#”Q1྆CTiawc1S l:iKT]$)|LK5abRcF%FK!aO辈8TӧXAt\JujPof͎C8M5+lTtI!e"Go,!z4qi>KB=OcpTRRa oF mxTDYCZÅĺgB),nafdm&JߧXtI!~bQ .ozD%O+;Ѣ͉ЍX;z(::$Q؄tt"|8!aB E6֐$tltmz_?Ok*&xA^ #Cfou4ᶭ?h<\!v2lF_EUpL"C?(TO 'G atj/dlCTo86v3b(وZ;` !-a'fQbX  f=4c DnclVVVQ2HLm)V[}X` {BѪ ߂Uàp/x|<$>EHL4h4$ Ùp)i#EE࿆1LhCfC DX$q"N>a;144Dhddddd$'1lIC@c.?П =fX$Ik,lo+#I$^z8HA6ѮOpDǘlle-/葜 H{#dv1]Z{c#QDFFAz$ д-LD$&8loBxqhX=?]j nJ-o 3hmUҟ*FFo T.e 1=ڣG%^ A$=hڙ,  $;ʛb #*lgw*A8!MhJ06:&'Q'D%D46cI=6lؔVhN0deh(m& 7ry8Xvx.btt~~'AlDT Uጌ M QL{8R"›b1 laA8Ύ.6C^= c^CKeEUA!k!Ȅ!Hp0vas }|G8QclKb4'<łoapMl|_~M8&Q"*Cxi 2olw3_a)4owz|z@s ӥm[R'єn)R Ք591LjN n^ Ѵ*=T(cM_F%B{ҝ5~ͷD~pI4mMvniD "|+!A!%6; YYQP/plKRSpPL&ɣYc(ؖĈ̌a6[(j,!-DC2E+V  1!,(M||%xG<x)K(hxFGB~͋ !.^XG%!"f[CMV)(CaClc!n(@١"2X@Edx#;Sc-nO9yzi7zx9p}amYRV qʨdnLr7ٌ$kG$zp~*'x & |o+͘/Q!yS9A򊭴޸Ɓ88s3·*"W~|o"7k7x8&  ?|D>7% g=KqNqDl3C.xE[e1k ǫƾp!6Vw\8E#HPZ9`@ġ9"H'M0r(C[jLH58-[u2:u2™dNFsB+M_6L54lM[(Hi)4’O.<ۚI7j;LvrF?@a]7GZ+Aw ti^A1Itz)R7}E,ۣJr|.{9x"k;Qۏou/:#`ɥ`y[\!RI!θ@!ð0XB &h'(Lf|`=V!;@MXν ""x:CN<1@kfRTCGhZj SvG\,4bmK‡y/i àǑW_,:7g9 S v1Yj; pCkйc fi V[7$ R5eSgEpzooǍdV!F-eh1]eL@-z1~>s=dJ2KO}A^zyByćgs@ xnceKVs]+u4XX.ʧ9G `@n'So}`"W>3=>E )c قD.Oƀ9߃O_^Rߌ%*"XbyP<*ôᗵMs\Kqڃ^'C*F>ĸĦ+{}eެ 1D[C40,h08n:iN9eUP6uG/{`xM] 7i{1,[ xĥǗC4^x@-FW%ó1%Iz(_y8nY&&ގxM$ coEEyh{8@!|sUE/olpYDj.I8 Z}CpPNľ$5{/-+֗gZP^}1 )Qyv|Fw95 =l[7q ;)n|4g0)).Em; VptO>!A|Ñ#CJJ;& `g{N2 W8Tq/:!E3:7RFƐ"/@wWr(@= J:9")u)YWp:0h,!ۮT.ku>HtF,ْDpx>px`@v. S4Yp>^Cafh2mO5c3zqBxqTxcE0GGG&Dx\w@dG4-d['b7odP(1Tvf m8'Li@MpiyŨXw&c8OLxP[@Cwް+[;D98DU;y0#F`00U:ƥu]+á{f^ bWtVK/pF 7b- Qd/x)xÍ`O8$t`d8 &96mBCOPYx@ix "!] pV7x1Au~݅J3u=. (_:8ԼRDo p~󔶕= 75 "i'Z,7 }0~~.^Sx O^ߜ |8| uQZ3M=1ĢljHZj{!k\6/X"[rm7Twǣ&+E~8Á=N7g' 8t=Z,(lt),4` @¤< d%e^M>t\󌨠yk7.򢦏 &;6 'LrVҮ%ף$rEc‡Vw Sf?;t8 [Uf@lk׌s# ܁Xqb_#AVֳ<xʷ8\(Yl1 j?l:A3WДuzÔ\v4`sTa]i@?vY7amIc>UEy'g=p`rs^pGr-ACz7X_m0kk=W E~c!#MX M>S-kPGxas]6\.Eڮ ΪrQ$7szOGK'SuxƺIGQB.$t3cV`@}Sa@>X>^7=rC&X.&1)ɽ$!M!%C$|& @> Sx7jASU 7׬qa b#pzȶ p!݄Ǜۅ,MWQF)Nksf۾L`:m&*"u0ˀ?FBzxx=PJ}B0~ɚ.<T, a 2?xYyC)`|Ei@cXMyB"ASʲ+S&!C`@r49p: [zG4&a3x<* nڑ.\;Ϭ%gGja^Wi;1W npxWHV fZ;3qa|{.`}4ʍ z)䈇J}?^0:4_a e@]1wLyb)AОp.nk٣KХ_ Tx<`4>׬Xu9pMק(D;xȯ=b }0dns!ۼ`"nѯ,ƈלoĠC>Y\4ax>tXD:P4 (Bگ@Á-9= o2h3^6 ^xteWǜGr^qT0 wf[Wotnr&@ Sl(4S ) yߍMxS}rҧ.5qKcboOsyT/9XK?`(EI]ZII&t@ OEɺ~&ȯO7fȇ1 єji`=:G`O(/MN$)g8r ܻm %:;ĴL ɄN)7$8&d~5hDmH"M5T^pb{oY _8(£+/k ^^Z2YEB܅5G|ȉDѠTdeY8}+ӌt‰xL8$'1XK1qɳxO* L'aS6OLFCp";弇N6N-Ѓ?:A y9ɦȔ):5+z872Sf6x4 O |dڙ)'H飝OxRHp x޳@ S*3FQT X4d_*(K:{h1~}c)^Z~5Eަ+bT:EA:sxUa9N!yO9\am@CJxLg&C!w"2H6IfTȘȤg A=-}eWZ0:b5rqSE=|ah6oPr@đD;x)xQoX(:oș\ d:#uitEk3e&ÅJQH>rHh>{/T4f%!o'Mp1ޚG˗k4Z3<|.R#\l돼5hJyG B`Gv~5k2KAB|(cBм0T0{ۅ=j-4Mk4Sjr%c|! EK㕛8{04%A(3N'm(nKsh(|q$9%)&q 9 >qT=$;'^ѭF<-&@5~9& O:3@o: 6,l=Ft/-0sy!DU5> ht:FBHƛqq ̨ ~CZ㈯dW7!߈k&6NAޜw4> 8!' L}p $(sS>H;9Qv:,"}` ASً1 )OYN0'42hxܴ,v[Mڅ|ew{Ӧ8|jWVzM^NYZOdĺz[C7̑#54__F^,0@b[ޗ؍C6["|^DvL|/pnxkt%|n44nMc1tv7c G#ĻhorUڟ% i&e!?,=Sͱ3>8uBa},Ok*SNAԩ4N@ DH/ NwX;"a{8ǜtdXWBc/N7DX;Ky6ޙ!vI}$dqMDQ`aa1|EGb{ <q:.(1H:fہ3[

1ONGWDX6TKAM~3JC):f8Vc);B SSĤ{D,Ie'-k@jF:}t}a0VF< waH=GDq9]| b$F"bagG&SdN݌k Yz 1K~ 1P&eV\MqNd xdtj kqNlxNQmt*?ycہ%xr _z5#R8nm kU p3@.Wz䲦je*ϰἲE(B 3.Vj0Ū&'z1ayAHKqc3akiS(H>o,$@<dzY4 2?x>Tsx#*mnUtU7} I6Aũ80UIi+\@iQ,yC7<-T.2 ,^h8o 2` >Ɲ%gMCj /E$qxg%[4c&Gh ^RS0ФEkEl!=b:;w44Sن/У,^oĦ[KF /[#}^!CN^㼱\cӣ4,{R'u;̭:AsJk`{b4$D(":x̓>3IKr&眏T)Tm1)m=Nl4t \M IE2Mm(uD>Q~ ,=[\B ;G,vU-5lWv`WZ4î+<Dn 6KTʃYœ\Fv˄o8"D6y6 Խm- nn-6{0,-AM8_O$(zOY@Dks\% zgꃶ;c5rΝyv$B?=ǸL,C:˽"1.^;"Yý!!>Mu x}k;/H?ـ~pm:Moo"[_0yqo*8$]fu GXHǰ n}W]'2 d7S@)W T"7)^h鯼"8|FрAwI㼴gc ~]S8!ZVb8^q 0dwki SZ&g.|ZTk k`:_n ECh=_ ȃ:6Fl#hErp S <D@Q XdvPP;会@=5v8Bd8ŸԈy۹(^6ʦe ;:Cgpqo?Y9eɜ,R,:qvfl ZZ5Tn;MqR֡!FW=ca/NF)3+X̳@4GP>&Ggfo܍dE$ DhY ~1Pd`HlpAہn<k]zF@o݇L#Re}a d*Fa]5 cun[821iG9q5#?tXy+[tK*R{eH, ߬xx9x8-,fˤ1If }\hxt"KxV(%h t#g"mON G$6Fao ~;lPHHAl)7] y9CVWS#tG(ɖYKpkbk.5Yѥ[ڷ ~(f ԺN1h|ZِTd5.7"~[-rWbUMw(v;fih8|ch:OiKpo{ƃAˀ$DBGΖU{=c8 6~f 8YXE5OXA;7xjţislAC=D'8RjC/N%'f!Dq H{N}cD %b/Ҟ652Fj4]xwԫI8P (u 2>Ei+מα0s-4xKTQY7ĥVZ_Yϓ? ѫqG90G-T!|cE/LpQ}8JńĨ51fEZ/S. ʖtbyɈ&bFόp`4=), 3!B NEH-n82)җ _pStۏf@px(7}]ԫb+gGn8ÅVB\qi1mp\0eSN)1i',c0=DpL_f#*a AWc1:ʒe!3l <ܲ9-oL-LKɺ)mM6X5Ӄ³MtȒ5 1 ;5ʺj$jSbg/r)G_(Pnq`6GA9^bmcHWٮ0h]K_P+PY\M+ĩ1JLS :4ŷAq`S@JqHzE|;N漑 Z8LJS}+k=BYѼLlZm%"n'qW_8bB^}CH1@pŵL✰ HIQHr vPtC7;hB#=]B5<2<-mBFG==d X HDΈ%sl^w?RB.u;&\_n&yɸ;4c!`3xI'á*Ϳ mIۚinnc(le;*sR,K+SlX'>(D D;X)f/u?X.hsý& u¯ɼom5prbA)pENFO.M..YD/8F) )[ZTP /o J2t;`$.(.^ Eg\BZ 6%`Y5;yseBUҨ'/;"|K)ٕw#q^1W%mQ?> Csm@UPp~pʩJ#Ӯey ˔̶)&Y1~#~\ Ǩz @Bzd(0YƜ! ~M8fA251ge NsWjNPpj3O(/Ht\ndyA2pW-I]NBWV2O{^/Zo uZ.:'au<>/}ؓ8QYPb.79X͖=cet'E%Ni\JBpP"6h5R>O%Ft45;ef X JY GnCxA6PpBi;q=傳R8^nqn92ZiTyyȊ{3Dw$͐@l̺)PkNs?GH\r[h̟A-8/Yy/j!t`4T'c2 #8Ŏ2$# 9*NrnrZB\6pכ5=sqy9[G00wx%lK&$*K8l\%j(z'R6˵^Ļey4W{{pDz0B8J' o(x#%댜g/a6o-‹ɵ*Łp2&_1 C}"o`6?/c#B~{@ +7YVcKK(.P.}I48{ܵ|_8%(>pMЂF4 + #=2Z9SF) w9@:M9*Zʹ-@Py8 dh{WXh jͳCc\[T5Ӳ5!'!xwiG@J.8Ci8ue{Jy01reQ&Z`a'3?"䂵W`|!ۚP+z*OB"&͆TrB%; 8 a֠1R9M;8@oh{i 4DqPkde ›4N8!*yLJ4pڱp`/c!n@>w.Rk:VZ<7A4RN}u:j"vwlvzq0_,O>f"<+!:<v9| !jjƓ ־)p(lq_ߠG8@= ~"g,[5H Th'7tK| v∆5׬P]#'cXRQ`}Sˋ;y`ni:t;N ʧ"?1ϓ<\ dG? B> i4 ZԺvt7d1eӧ߬_:Ǯ_ M6<&wM (2-4eiFfZn:^!;88mLuT pD76 O rDu!@̬7`4V}15(G@@*:>zv'Vkݔl4y sADVۥV0ޚ8.߂V1pG [Nd++9Rk!e `i$tE]e_4u"#r&WN ܃\qXySW )_c4T t}ޅ>2"g"v=,! "zJ*G ~2M6OBqTXہu(rA;؀ybJНrjzΔQ fް@[ƙ ݥ} 2'X9.;C&-Z7qƸd kÏg;RŨZWIZxX|Õby37 &P! hy0WǐR iP-*ܢ譓 {@С%oX׋n<ƾz&9Hٌ;#2vHn?^6avDżwp"KaLX6yXCPpO1gv65ggM%2b.*6h91ɉ Ne]]WHn ׀l GcZ0SD+ /#5ǾFN_n{yU F~j'ccx#N IxɁ>OTi L;nKwXR*2R;֒wXNw4x)` )8͹¼&PFt'@M ;ח%Xn򩫼Dvx'Pڟ! m+!{WDc 5k#0C]氻S#RvF//G(7Kk42/l|g)7׬p{ZMaF-!M7EL<5͒xaxm%,j PVn>:P_8w6;)vH#H`+X]u}ab!nylC5X& vǐOorXMht3(ɺ~sM[|k2"Aɛ*{NlDG=Nq=.Be[ư(0KY)%ϟYXRX<7$YpQ5=Uf .iI=U9!N>2 x m9:uuXQcFW_ChSϠj}8B 0&h*~1%:=oh [TX[8H}d9(xoFTsu#Ǭ hK!<.epc[ӌ 0t01#T[p*Sb71iTqiq)8 '.Y 嚻;zss m? PkB/G0Nx%D__)b4VQ^ۈ%(\OiC0kl4`;W@dG 4uf'k¼"Gd_Wgꄏ 7d&I�<oEY g]cf͐i䘬B`W~s@-v;G'rEbx{8@ơ`qBAxÍjJlrej xovl?,h|9\!Ec l$'[IJHNw)/Zf#穑c#<Ւck;i o$ϓSƢu^fҟ_<rx㌟J4ghx?'%hj# V(ɭyG #W PyO 1U4Qy0@GmלS A;x DP"jT#MK Y.=L l N}&hB6;NWi77a=Q v#*=\*TOh§TDFFK|k张hR *&Dۜ˸h8jť$.qH!|s,*\d=2R D/tL х1(R^8dgRB|9K?1;=)f3LViwݑuYJ7al`[dxN#1|X\#~* TYcGp^lp#`FG^d3_I9>3T{cf,: xdsGJ8o*Q8'xJ2Gou5("Ӏ4E{`$$9c^o>P lMr0';z,#3*ʘ))#ׄ@h$ }\I>F,C]=fJ:_7P "|qu 0@U gRa\ #a04kEH$X4pnpE+F=:dM }`oa}!$pOO5$%ܯT>w+˜Ͻ CjSaU>@ң%:{RCӊ`P ؆`b@ya8WL"GwX٭Al]sĮclP:l DL >]Α;L2%V~ `wjjX7*Ix$,.M1 뉮j-G0}ٻ5dTg4|^~@ڜl8p "eQ$m(nh:!{ZvHHDY韕y畎9=$kb2*/:ĕ\({"# ~hNgz|za;]> 9ҴٯtwP)vIB SXt n6EyO;a L+z[O?yRY|D}5FxNPa FgbѸ kA_=`+1PW7Bs7|{* 7(?7"Ǹ}!p֙ܠC9#ตfL:[̘)-GZd[|<dpH{[>n?Q!Tm[L+f`Гkcp G0r}qG>{1‚1 8E/i' Pyh)SO&Vl 7Eh%L Mƪ%QIXSb4752miQ 8&*'%OhkߤBDf{M u(v1=lhN׸,yY ٓXIʠ%]17 ^i0O;$"ymaGc[ [< X(fZ1'Ox͖!!`5Oߖ/P T]/#ܨ!|L+t bm]h]MAtLg2 ʉ!X f~l94fZkA'< :;ǥŶ}J)`FDhU`=y!|81;S*d9 S5]3Y Ѓ]{°-zG% …v=Q;2c)a-w@hnjv-una oSrfm*\J~dH}6)x7Tlq!ؽM g|&pN4*) #:) ~ Mg-:^6pOF%1i j)7<ⓟ%t2#Q1މ<v@KCZ|Yw+ ͻzŰ]~/2\3X@A^sGnsY %N}= !e2Rxͺ96`@B4NNqeX€BC ky#Ryݞ'8 P@ޱV_Eg^ P\|"2u^{EbߎSd^`P sr<䇖FFو&SMA"F6gW (#p]+tu=TFC {hh)Ĉ `xGa^l߃m G< ;*#dM}^`4inxG!& tO7YP,x02Lۺ8W ju(= .u9Jli  rYR8YOG3}\,h#8rl'(x&`2[E-JƄS51%É1wwLmnTڕ(3,ÉTZ QޱWѳM~q0O"rzwcCweL^)q߯#y y)q\,h1(,$h}a"8"#§]g|>0{j w*>/Th8Ăʅc/iQA}s3S!\Dye6IG;r *{6_;y @^9ȣV#.᷽#Vucj? yrM8W4 v O@?g4uY`b%azʍ"k[pUĸ3wCqP[{y`ͅL3 y=c(yW8N{#GDp4;q~YSs4.`FO NJx%<-mS?X0}aP}F'R͏|Ût$y v|3 w9&}Fph,Hh5QErsb:ȶsOB9 t8ӅS=8?sO72&>t3l_Ll}c@k6LK!E V^uY+u8xz)458a` . Ɇ=(aH/CO&qnxb 峼Ԣ8IZ0QxI9#8<h0cq+PRM}4W ב=a>3AA`qY> 2v0b GCsHGJ˗ o\;rpǤ w5ϐ_WVÏ\-Pkr8G1tpྎNQۏA| lb ٌ'M";1gL$={!BS9C{c Iue?QRݐ @іP֟JUy| R(R&m`7oC6-" y>^ d0 o{A61 gIv=ӯ NcE=U׼C?oFt2;2'uSgZ3 ^q6x>^|"*!DGs5+ O/8M7^۽SpD`c'N3awp4Ѡ*ʐFb (m.Xn #9ȗyWMbЅwc""y1J_*SK/0ç&bGɔ OK[4Kd;?9sԩ_TrpaFXalt\DW&\%Lx_"r R:1Qr&)Ǭ.Fniѿ̶.*O)fO 4k1%CaW"upwBC)=l眻 <}J~O'Fr5z7M|%&f3t#s݉y$'^`;t8u!_:0X8۹4G{kztw?e8hGqWQ뙌Ef>/@1.0q@ODT6¯ *B1 {0m:-yH=9/;j`S =&Ia\gg&6_!;p`X:GvoQr{ CR|e4 v $1NrrOq9. ;Y5[/gоLO w"P9_f!&eZV2Fc3yUg:ZM m=4 <{uSva%x-J.0;DO&`A^'όEu[]\gF/j>LjT[h@QF $IKa 8Hv\b\,AMЃS l{!6nuG˼?d}{ yr)RC%C 1hBmCz@UGZn*=SŜ&a&i _7۬uZ׌p eWyS)5ǸDӌ(< I}oޱ"b~rl-Q? ]⑫7gHKyo͖Jrдf9})U,^0.'ۛ1 KapJ v8[߼T<̎4~jGR-/7 #iT^RseCg</XZch@/ۃm@{U9o*JĽi;Hz^߃\bZr3mx-}2HzNTWvzu1 xDpL E00K58woovpZPyrzyzR#y<ܪ2Z:.e :j t`_|ʻ` 8{t,[8MCJ>N+Vq23=~@8kc_]]e9lovkmark-2017.08+git20220909/data/textures/desktop-background-1920x1080.png000066400000000000000000001355521441550741700250020ustar00rootroot00000000000000PNG  IHDR8 1qPLTE`;Z?[@]A^B_C`DgChDbHdIeJfKgLhMnLpMjQkRlSnToUpVwVpYqYxWsZt[u\v]}^~_xbzc{d|e}fڃf~gۄgօh׀l؁mڂnۄo܊o׋p،qڇuۈv܉w݊xދyߑyےzܓ{ݔ|ޕ}ߏߐኌۋ݌ލߎᐒ⑓㒔ߔᖘ◙䚛娑ᛝ✞❟䟡娙档窛裥䤦媞屝榨槩筢诣곣媬涥筯记鹩곫밲빬粴軮鴶꽱뷹µ𻽺ƶ뽿ºĻ¾żƽʾ˿i IDATxiLW Hhqm!UkZKRDV\pq4PA,"1B%*mԙ/@Egb !.UD2ygf+73眙k}T 9* `@0  ` @0 `  `@0 0 ` @0  `@0@0 ` @0  `@@0 `  `@0  ` @0 `  `@0v]e9Ikc#C^&J޹?7_[l\'&&|#GVuw5ꃉ+MHuv"Z߾Qux踶IF-L {giU뒓wy`cN]4^q^ ]h6c8+o/J^OA5,h bIe_aV3 kOL];gڔ)&M   }DkxuI9EgZxC3O=M/wnNTrW{xtg-:KWQ˨ `~"mA-ck^l64dh. bW_6_9{\{CyjLԞ>}8wI_T8 `Gy[Z֔f'361.Ek{kscʿ3f]/͠3JM͍ͭ ;Vx{0wG5s p4k&p a*KKX<9hD ?/w7 <{ 4<}%Jݺ+O_='DJ&i֦_݄jCnuWĨRpʛ lH `QQjf^Ia 8$|ڬy nQxpjو}}{x%ݫG/CƭH]eJߺ37DGɴuEq2h\hwʲ}Ur}gԟl`B0js6,"/a34r /0+ƥݫ0_$?b{)@VS-I"di,TR~~WgYGmMwBCﲵڈ'98ͦ_U) Y'n6r* K =oHhfպnjtdK~O=z-8zrL2KH6{% ;b`<~n*{u `iw1k&2"\4c&.5ș,ǣAmzwtS^>Oݤ @cQ~fa^·ZYr8mI`wCk䢔Eט4",,ԁ6+vka0 k.2RP7/X 8 Z=&Q\^?{O%< _f3\;̊.f#Z٥ok{+GP9&+~l$l_i3L qd"4q[rOџC~j*GsРYL$[[_U4+ J/4ݣ?_AY4}E-]R5?"2ifUqLR`kQ`ow7`@TUv,gST,L2Vպ;~Vf*% `[hvᔰ"lFx'-G%yFi,l55EB0ͺ̂3Y E~-~}&$iom>0_xN)m%-uQNߐZcJ.q~N*`Ubn2_+^D|d+MbvS95>w% `R|$p贅2iP)>UB( ǽ+E9kg d$T}CE9y Ry\'_OKYvtDX;-$9v005Oq-"^fKuݦO\}ohbnK qAr!ZZ۫r5>Vٌ~_mPWpȴr8-]_7'pF=@/;,69D1ޮg Y}{N&ۈR1SrQ<|X쉟Gej/-K>R2nca) ɏ АFּ K7VUI@Ti~)8zk!pnۂ!*psjJ O#1o}WLkko{``ن@ڭx)4IWXŰm "R )H9_n-h7N>ӞrzMΟpgf]!)ݐ3xy/aoJ:sۮhS*xm۬~{ptژ?m9ԣ=\pU_\J,J{FK F4Ӿ͆|2ҮOپ=ouX$'8$LpTvGll̛xg|gjy bn:c4_HZeng J9Bp%.J`xX 3XIå8uL<@ :;ǿuA<0Jn@zoѕ,+E|,g9j5b"VP,|?.!gPόu$-z1J 0-"2>mhh;a7b5,VC\'}dqi~N= Z2Z#<pr$}dah͖bCMOQTKr74 ,]N*pװ{ ћs iܒ&W1+O9=#HpN]ƣ_k<WCqUw3Te 8jhfsucz"Sp>\Cm;YLkWbȚ=ڒLM(Vo) 3FFMJp&O;0o.Q u:`y Tڧ+ֺcx1d¿v``ʹFn:oث3\Bs_đ.;s`p˕-0ZrcZO'vbp WÛcJt>m)aWON `c.s)rg8?ڴ#ђ|#;eڻ)V+<J^ԿgS89^NS.rÙgj`@kri,$Kc'9ڿ3ƿBsi<0'"Zr|.4 `*gahrم~鷩RH k> A+iB>8翂PFԿ"ʡ~e>RX i$@#k~'t#H/#,`+6i$@#>_?FcտDp 2HR ; 䏄w~:H4v `I48r)-(Q e) C@x3Fy!Gʚ| WqH;: ich%&|jIGbiE+-or5 `S_А?zȝ=qxH  `eTe$$GR7KK,@1M?%< `s1S' c^ygԸɟN8{_~+g~2OG3tȠ=;X}J˵\}G @%fp}~c!QoˏG6~F{vpw*hw# X9j:cOXp^#6Lz <7&!mҗW\4ݹe>֝[**Jt:ݾ]YWp`E_nFK,@1oXf[pF|il|rz'&-]^5cڤIxbB7y^l  `%Ҕ) 5q[j\rJU͍g|ma?$玎Z$z|"erC?w-|tPq6oq)Wh=+{”H=U4WJY9u̐gFUXqWԜe5Mhik3|'[+OI]0\N_*SJ?n'S O P^nK +lR I cn-߷=ޭ1k rNvcLG(JIj߷:`I%}hܐ^fx,+k)q9,<|J-{FUEL f?][9]9<XI*gUuMv|Mkg sWIs4U٤nӟ.PRB/O*{N=pX1+NU\ۊ4q{]i[n̛!xQ. Rγg.H9|]wz~w}Rg5V Jk]|"cMao^ cџ ICs0<꽧 w${l.5!X)Vs(ռ[sվdz"E:iMP|<,JK9Y1A} 6+ON-"Uvۘs+l_=D"6ЛZ[[*w}S `1,H@JnZ72.5W[,CӖ~?Omʛj647 _g~_fMVR)JpwybzLNp^D)m_wnGh[2KK~̢,7m5JyI&^UqaP2NЏlz%\T{./q|o=dbU5h-&E͚DצۋvTxT)koMM2:pX܃tX;SzhWJr7j]D 5q2FqmG]}@X^&*Ĺ .ل,'uٯ [kۢ1i9Q!rAy^{{áee NBceY5(jX 2lۈ&ԅCe+Ճ>6BƤKaݏ X&ZNϒ]zICSS*X䏺|vlz3樢■HM%i, 6Eݏ27zxOCVw&wտN+*~^M[ yG՞3 X&j0>Ga]Qt5ς=A5r_0@)cIzWXexw 6)whiF+2=]}~J7I ,KsѣşSftѳ_"oP£Cd]o,@ݧs1! `ɗ֔5V59%[vs5:(5RRM^$@S/_gmcL_)D?zV} [ ?塞كU Xj)[ ؂k3Mf&70YwbHE%a`@r ^[~~A$yGGxU{H*wk>0xi@RQ9Ʌu;5ڭQ2y2bF{KXQ? =mOR@Rq;9.aءg,[&SD@7kX/"zgh/O1DK=3do={*rN|hkDC+6&tS|e4,5 `@pLǯrE9G ]JƪtI%2<#zS(Ik\N!sټ[7bHZՉC2mw:0$~<%9 EEI?r4RŦoQoth+wve1g'M:w5;OHi3\s\O*n~ C7MR-sމ;SxQ_#$HuN;qDiߖ} X%5%e#ŧn馓?#Fep  nmU鷞F2|!N?zn@!цbq1s&fj^?RxI7B-SOJ\`{uu | (*iFjKwJxz$8cN,)Fj{$핮 Jr30 `&?r^?Rrɴq$#vQ2R0W<,Lt;񅜿3G51RP0K3j.ߎƌ1]ּo3[ m|4,k*4R#qk]woBf `g IDATђ+=;1VcMHIiV ׃4WԐ5V,:g[UI*f­n24D,9z .q* o=4 ݨ+F.@kGI#sLJ RB@vØ.5-̷գ #Aނ ΁OnBTcNl/%i1džHezJO?JiL=́)D fIr+bcHiV÷`u >~Tǰ2̀BSُR\u!4ҝݟ ǐoZ:'0 󥸈0ti!G H#V!8!x6 N `P:OkҹJ?=TQ7*NX\tCz cxj$d%ɞF:-:uf((f@TPQmLU/$kr^ l@?Y4_)v'Ql8wi[H4Rņb,{Ql3m hhmfOQgM# `Ѓ(}Jopϟ2̀mHG&EmUh|isW^p>b7b62ʀm.I\N@[ "IF,t e<"z 2 `۸V-\rm"G2JL<"W::c 6)\Bh GDV̽ɂy#1 `($+e[CM6qJdۅw] N_&aJ2 xb}KkJQko!LݨzaH ®YTgv[F!${8-]4R^`;tv 2p0 `h.P׮~& 6LG>Ys8)e1U`UĴڹ6!HW݂)-t[Q\[D}w5ppOlC2e3оL Kfb{~ yp E\'NQ;H 4}L# :N"Z 30 `ʉ'5N!k|/is dWjgZ E\-NIwVsxObYF:91E  `K0&/':fUC+|n$]=ӪyQy\ # 8IC~|OH֥#r_>= [BqtFTA:PiD,$ox3lEv2j?Vv *=b nf)( `K86E\ 86h(Y.-O#^VPwf9kB*t⣌. ` N&`Mf*Lρ-N#V#`/yh z? |nTd*-L#5'4S1Bw2ͧ*W\ Pϧ$` H f6},ZP_n@nfls xMw j`Hmb^ۊL/!/3L8gNEideQVǂ*௮҆rsOl>ŀC[S\j\-H#|),֦6N62g0OJobN%f6 ,i~&,!k]Ia6gsA4+~p\A^Y@ pxT.;Yu5i C?H/`:M!cO0R,Wul/|AO5H!5 0v]t1Կ/ b:* p ,/ 䏞.A[sq8;NLH|f XŒ~E'4zK_[ŹOz 0 `"fTrLg{ɢ_76}@1m!nU[ C xaaS*51=~aA[EI[kL46S/uR)H]eڭA fBM|vZ[O˳=&u[%b&dL `x^6TBg}+_?z*`Aπ˙xk8,hL `\#w!`h7+WQ'#Ճc`j]c退gҋZS0m%W叞l!( 3V:Ma'0 `I $[!`0d}?j|Mq}! :[Nki 4l6IERU4?ɴc.+Dޣ}itofxpA&94JW=_ t뻋ٷ  8\%zWJY/vuW[!ଏ=LF&L,ͥ xcJ{_ M1.-C¶"` hԯ}}1D\n_]mg X,Ž$'X̚ L `KG/P Y[gGO^gycnӪYR1B̚zg/ |'ยz&Up@cZ  8K[MPknqEl pxW~3EP燾3R*3et[V\<ȤAi:8LuWVY r2a_gt[a^^̤3pU*Y l~âjGX r`j/1sRFx-*grտ珞 ,$cF#lūAk TQTnKעQ'@AiiմⰌ-F%ˤ ´iL'-urrlO1/{V-Azbl >*C(/y{%NΉ Ly}o}~8>dt[$peZq!Mײӟ Cż we$jqa `-N2"eLTԾe'mx `@QSF iaw)=/cEmW(,s7$ `[$NQ;/3t&5Lԫ),K8r#G[5"ژZw\ǯlu^Wa|9].[ 0 `V#Ӷ06le\=Y>1 ?6^t 0 ` &$2/_k{+I/ks5m8[N0I!m* _;:HkƔR^Kfq\8*'kARM&~?dz  ZN(k̭M(}G'R}R z ::FڤAb|iʒWSYw@ю5TX/)g[A[1i[ߍo9~a/czAڃ[,#S8*j <.kkaןĕ^bͼs ~>ɫe[CSf!!Ie l wiٮNN<A i- `0 pt72XAcք>j_;:1&1Wњ"7 0 `+Y*R֖1nkЈQ6dV0c=2al3mSE dg$=L*z#,zvہ+ ߃/V#"$]wVxSX)Pn f@vH#^2fi$G4+X C%tGǝ׿Y 3 ``\%^KcF4 X-$%p~qS,44cÍP P|;ua֚(wy7L ֆ& IDAT-Kh=M#I?Zy 6G~8ȴ# tl?ɯgǎi$GD'T`?>9Y5h@v3ppHxlBP-$yiqvhp7w厔iDR l@i$Gh;nz0m+i4ƭxNH`4 .d 7 )$HUۍx)46ۚF%L#A==fU#jMIXE&lN#ɔ?zZg /s.zS^kXku>ipI<-n=5Ĺq%>J+3F] ڍ+'ISoA,[)$[) +K"+:/.% خej L-5 i$G]m$,"5H0^)l_I#9U̹-XF3ԅ_^ߒ`dFv5 `!HHaqt[.{_(!&T,?)vzA@vi0i RQʔ߁zP7-uv["$w)FzHQ ֕J}CT۷|۟ksB2wg0-44${)gW{J ?UΒ `\J! .6aaI.[bMqV,sݤS|("$3?n4s畇>Ŀ:pkB%3Gj`-h1| JCFj/dY\lXMƝ 8dʏavI!MWI$.pbz$5*0 `1h5 8D>oS lVɭl䏞r'G"_sje<|sK4-cX7̑3~ Hu(ojsNzPw3Pχ($ R8y \wOՌ4G] 3Tu;+vH7iFR;k@h1TR;w龱P{mIYNOJ*ܻ:Qc6@H&\JZn&-Lui$叞Z"o}8I_ʿ*J`șpuN:uǒfK}Ui$叺od5Js#XkT!SuYմ/].+Huv io%Ł e X(5[¥5לpR_3IJI?^ɣϦ?"CJXT/b5n Ҿ*A_@Rpp ]k^ M#)9ԅ$Ոăΰ%-U~_XN`𸝧n;\]̍+7QHZMnpx X ."[~ڸeC):H:ʁʬwsXb5[ASν%ɘ*C_3y0饽cs7|FRz^58r\(L=FKEP9 _ua籩W [_%USp &+]wEԒu~8&OKFY|YjIir6cf+^hrE*b|~s$~[jGo48"3./O|KyT`tȦ׎ԼIFZzCm6>&Q HuMh@SՄ싎ts{i$`Zvс~J4,9g0ͺ?30wl 3C,n}tZyl9}&I._ sfNŢ ŧƳ˺`J=yGPp7#d&Pu ,=(I;ꏧFϰ蝅krXϐ,vqz+[L''u X =EW~,?yo,UߕoTDDҝҽ_>%&5,OqNVLaT&mҜ+Z X]L.yd7x5:,s"5𤨭eJ6{:]}WF˲_ZU8YϏV ,@2QAf,H.ڔ3b6H%=lhhW\$r~2ߧ,M@rq(NnkRxlxwm8Ad[*+OdZFz h@ѴcN  ]oWRcUtSғ+JChUR+[MպTI4ˉ1gY~\9kf"jYOi7U7m:=;Pc-ѱ.I˙ْ_j.fc[`J,LO6Gh[k%ﭒr  `yˍ UfDg+IkXOLfn~}$ R;?4(K1;2DE%gJkn;HcwҐJnF-J{^>, } |% Xvg(͏EM2xsք6iR=G4Jt6kn?% @k#pͪr#rhv~?H#utKţJGaAVI-'Z E+ýT@$Nd6\,Z^Oe}Հj V5 pxĜ;H۪;Eϛ1EȻ!ԕ~uIUl9b`OO2 B&iRڒSXd}nS}H]1,R >nMWZ)K7-[9os E@ )e 84<,"*YM u{[:#gw.}9;xX/7Eaiv" `x˜%2+6yϱ;zZZהf'I{J>WM&-Nssk{|Hc+}>4 `emBISCY.C:XTuݚXMtoxNbuɐ8٫NWڡIGCY.z7`Zqo =(Z5)DD&((x&K{u4ݫ2n>uTKԾ*E܄ꛦFKs: Ն^|^ Vux 8tRDB]4ްRef&ZY8o4j} ~5h=8ۮiSSK_\=ܾh*:1mm >Awt'd߁ %d̏FY\ %ZƤ "踶?p1wt,6 s{wõ/~US{qbBGX1?G݃St ~Ԕ#2;4I>E+A1 8+pKݩ(D2\_Pk?\_óX{#q _{g9tg;p\J Q_X/ J kߞEpkOѕNGFeѯ8*3& v*ͳ+Y+w/3uH{mOzds\ntѓ4aŋvQ~V~gf@Ʊy.~9]"Ů$]͔ԟ0u;_J/4, twH[U2-J w}iq/%FhwsYQ\ߵlݽ{ Yu `h{h;RhUZ2i++% c A< IXҔlf˗zlSkæ!U#CY&*RhnX4!W*FTށ2Plha\QֽySαϹN/_{99b0?DoۻڤDSњƫx@/2tRbcq]72 }foS?=k_uRߓj ҋLׇgx@;{㳯}2u%}⓯}6 e )/I)Yy1coϧlH_֛?FT1/U%  ?'SmϾo|7]'?K#g +3s =iD=7ݔ;+_{_!*>YwoM?~?~aџC)wnn,S/*h3@ҍ#|_{=%Wo|G78p!U ڄM/h2 bKOؽܗoww']h(c޹@Y*^ pRO_O~J[>O}_~WUUw' Rg>';oo9S׿Kzz]#6sKUq3FO{V t~|+yM/_ۿy%٘yh(jOԚ,ݮ3,¯p^_kyzWWf`ŷ4OKfgO/yM\ˊww/g3~Ko|gq'}i?Z]@6WwsH ,K~W?wJJ?7p'2Ph3en9\aMgP(F@o~퍿{$Jr~O[?/\ ؿ9dcj=QO.,F[vБSmwXN~Λڗ^Oڧ>Op'>Omk~㛞pۏUgTP1jiy!@ǯwGo~ڗ_pX˿}{퟼H "1y]-ϟ6*uy:r2K+7Ot~85ÿ@៽o7_}/^@O|O/>z맾?|_~;?xvlzc掶3Ғ(.9|3Ay+Lڜ IDATo?|?7o|+۬~ _o|?_5\p8 y_|~n~^ l6q +8wo~-l;%^˿տ׿ޛoӟ4;{W?~֛~}o'o}G?z뭷yl#@ n/|'?_~?p.@0 `@ @0 `@ @0 `@ @0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@ 00 `@ 0 `@ 0 `â(B1SRc˕c78<:69S ,{8l\}nZn }˨OJUwq{).8m f|ˁ͔蜊>”szPZAyJ9u}p ĕ]}epBxXOBqR_;OԨUvl8?:׺+j335i'M.wx&}WۂwW6+fff?ijVkP[mlA9sʙ=YhhZ1לWBÁhI?pzoYKBZv oЬ w721K^lȽC.ݣԉtМ‘MJV J3uَ}n7d_GPRUZiur 7aT!(3MiݝJSv)5#"cFaGcϬ%^P+;&=XWs57Fn**twv#i,\2陼o(ȟP6尜)>C˪N؝^ƊB#7ߟx2zmxK?Lwm:)*Bꗐ"nWć:+;+SWe ={AKg~|Ar&:N%@SKWl1ל_=#a }gyt|bpa*WYㅒ6ŐIouvQd-N(JX|Q-STκ(Ǿg:{W ɧrb?l(tCI؜4Qa>Ʈ#+!Iؾ>}:7Rř;F}~WNoCE6GPuc'wo>7~>Y>Q͑;!`iu`S=9j X!]&cTyMDC2^"H <9PEذfC)_)Tkzܛ'tvWjԏ,}ho?nM)$LC{n5I3~ꙐKQ®޹d <ڸB]#? .Mt@>lӾ ͧx.mCζ],iZڵ %2^c;ί):7@=\Mr ќS'é ,W9NT,dTH9V8H]524'mujKۢTgcS'A44˙ gɝq0"` *vtЫˣ(6<Tn{nnB=#ڍ?S#s<tѐW @Ŧ{ ZQdV[kݻ+֗nLVBhV,c󯛂L?5]o1N B)Se)I>wj;mÒ͋6#ߏi4uy^G]̹ZJSVJ!7BLږ-1/ᵬLM޳J$k]anAir͢ɺ 7V$)˴[!5$kz^V=2s8Q UPhp?Qsi UyK: :i XQ眿b7r+ƖI `)9Ñɲr͔ى$;VHk-}bZD{@ұ6^ WKN8 X]1οjɓn*yBݿekA#GA*Tq7M 0)@R1e.:J XmzDJ2ҪSHZ9y-$d|n;Hʤ *W8)*8vKIpװtH{yy `XjG=s4yb![5kO?W>C^Aם=6ɻOtkWV itT[KEhzzZ:QJ.oմ+{bDZey2YN4w56Ӹ/89S{:4^ol/fܿY뒕5x,KpLޕ!N`#̻ƨ ׸MnuܚWw5]۾-']o+5\#~Ow,`!2zi'#83qC6l3w՚︣[dc sX-:sn3bZS'WrX*;^ZNV`F]4ر?'x:T[zfaE}:ɋmgYd zYs:,n]F2hhѵB/%qG/9_ u| ,/N^-|8{%^[Awe#jy%נ,0Ӭlg `xS*<'M|ǚm.QҿI..<ӀUtעwo>W}L]'?dX6"! 끵3oa- OHcci- ǯ OxvpW uMS5Y+b יG^>~>L%ʼNczXž6%a+AYo]._%&+\Y։TEGgmI x~.I)).Yx|_T.)TubK)-KXڝaoP}l7i+n+i` 6uuq}yY >޻ai%a}~{$x1p$0n6ρe& ^QwT9bY]Fp_á(mWi_BΓ)(ӍTrr,c}/vޤ=E :gXy&+zY刣驎ށDNrR[V$_b!HْO،q쀩"z짦}wW+rqIs~rO ˿ECQcU9-qf̅у2։nm>4z&+,+9, >bh<_2.y!8>F Ps)ߨ!lsfM9g\OL^)DwmިK#6wL𹣵 ZpUef7t.KEŦ h43N=12BȩV8q6beCTZtGݵ`?g(wTHk/5T{Eu(`͉t"N &-LXS" m[^mӃJ?-H4UZJpļԷH%>GlcpwTFF]5wrwEw3N[w|BkUFUVBC'd'3TEAWxY8WF/] } #Q7B􈲭 ]KGHS_ZdM%aԈ^\ '^[4hy~O:Y=M; |:ZYOBoQG 4W%^XH )*,<'J.$~-Y&}EYw4WL{m6BR^iXp^Ch'X 8{fU=H*>(ApkL&D|C;Žuk,Vp* BZlN4nEc-7pxM]>T]_^B(g2*hk |$]_xJ@%uK{BYnZy9F%`d]|o1O9b:e'*88(=֮fkc `LSE0G\a+EЁh)!6TE}+ FC-Y=,S{eԫ_NX': v 4я8<ͦ@`'y/+:9S Ò8F2SS{cNcyXm&~derW (UbLHl]-=+(5lZY?S]%,iA y㎂rM9LY% (SQ3 nl; M˫𚣁Ej;%]ČFJ+./ȼ']k:xcUX"DwuFgiy"ZQ&O?̿* `P`qC]vSҍH,ܷ?MVR 0~FޮݔM\ax1\ ~a >E[)qˊÿ@>{ GiͿNmV؛8BT)UOnuк0j ^!ҫmacGյ.2pqQ=v/Y.jw1oŀVjH `g6a:.rbaW ]I.Oo墂q09-""fZv `΄z((k92!4#ܦYfwAl~a.mex"; iIp`{wuB>Y S%}FP`J`8FT%ڹDůhgwBٸ@>{Zn'_)*`rًM&iFS NaG-D{YCOd|Hz^ȥ' sل|/8eENGy+3pԀ6 3*{A_k7ET? 29si5𰅼_԰㠓+`"ڄ'Q2RjiLO{GЛo!`ĬV xN1}JYi6`$e^5#Vu0 *L1W֧[4}_ueh'ń֐u?')(9( NuRS.nZށuF2H}潄7&?v#\rl'VLqj/%:# NjG9֖EF$+b/qV. nzU4Y]#9Rs~KDCđe1mHuY=~cuUKYZ9. B=`A׹ 8,p<*oO>Em <߯J(%CQĻQa}ks `xPM^퇦z);TRý=livQ$)n!nRo1J%4Q*;׹8δ|Ji#90>(H ݽwClOo|״̎sg󮸹 84|e*`iH T?&$&Mv8_=$ S7)G+Um5k V:y2VMj;3jn墜@Yh#+Nh\nwttb{{ L9I687-{HÙm攭k<%fX{b,jF#`Ue5f胀=lwYil6)h+0*xȑ&Vi mpca2J^K䠖Ny?d8k_]rGG'd$SώOV+>I5VȧgY3=eJ 9/zȡtv4wfR60=V-y "Y2&Y"{?7j8"8UE|`s7g=.ʵ'S￈~31m.e es&Gl58K_Lc殙xz|DfC?{XJ>.ĐOBBv"*۷?$Otԓz5ɴHCDnlFRpx(`!#<M06V,,GpsN~b^vꤸMNP'8$ u'<(bD V?;L6נ%xL̋%L<]p>wM+M%mq>c9S[PD!몏ez+ɄJ'`WK<(] 9}ďM )x LC$na/9+oK*8Nڒ`bGI8o%:; &`T0)ˌ_]c\\JIPRq& xu&v#2[m#ߴ',ڟ<ُ7\ GR!ATwG\2&ꈒ7[*۟ጝ&%XXH *ӛ:5H(d%`1iE}AӢB޳EQʭHY(r]͛r.$Y=AX{>J\IS7i#ѝ}%Yt"y7J#-Q(J?'i"^l?$;!v Ro aqۺ$Aelz;,KhLrQV %ȤMiN'o7D>.@$ɳԓg,tiN1)mWdQYKj"m~胀)[\4rcg'9IUQ@7T rS+1(rp˺! U?k#62Y FL1=w"O-D}>3:RȺF>ܮc;<.NAG}˒Luދ,&untR)9O5>Zl진iF3frbe, 2;Î(د_n=_waqjev ~I߬ m% 8B) ֻ<š&ޔb{9XfƝ&ʛ ]d18=o߁Tc}M%<H%ѢjUY?HkmUrr{V!`2g)#oN߷赒߶ٗV]㱃jelNI$ X!4QQ]IL $ L+%N*QvKВ0uC_vE]{@ɕP\>^e;f%١@,`I{.5= uy3m͔osU~ N%irX؈/,q$?v7 Xe.1^-̵"eۧ!kZ#9#mH$ʼn 03_!ζyɩS EY+333pѾלG2jw}uɥǎPz!d|Z<:D>\U{ŋ hs?ʃټ>inMw?[\S]CptNT EZꦴ&OlC,24?iِm Zr t'䤣Qc `JM̂]S2sN+Gvͩu#`LA3A;ExRN L*U2,B,(nXnGɑh>yRRʌcbr E V}.`/:@|}o+_jLɑd6=zc=L{֊Ӯu)v2q9?|ԪFs>5H9W_|ڐD&@2cN=˖1$Ʋ#;@̿V 2 3a/xۯgܼ* US sxj2k(|GRq&wKI.+]H5@i-^38HGZ6g5g'O_I2*)Oz3ePw9Jȅ(uЭLcHE?j#$q`1ȍ:r;q.ዔNqݱsjWӐ%Rc_`QR>'AGőKll1ϫ9,u(B)x=ZWG(7RuZԦ'7\V(~8'$ ^ k2enUWt6`$><,) 1Z90y&ORB,jmyYNq㇉+T& hϺ6?mҊ+ޠZ xz7Ӓy ,/?V砬xLGIJG#GSOHTXMV+$I١pJqwZJb :a_Y6̔^ȪH뉏)bMA]=c,ǚw#'F2CYei{GcCm~&`I2gZD=OmbKΏQ}?(%[M4r4㡅tW!P>xJ٦/و02rg[N@|I~B#Q@|r`#ǎ?[qsPp=6"2r>~;hᑯY*wQd3F>BQDn#X)ജfT[g^qO1V`qb0"fDN@9w;YS;z^& 8;g# {Al2n>qck=1rTP7[ @ us+p6xq |WO+铧KNZƼTTFGX{꾙s' O>ؒrD\ĴʴZ N\PFJm ̖GuGOFRmŋ[󙨀\b xݧ.r[w\w_pάJX%KY˛NZ'[URtH{`$] O7\0ŮVHA^%`"-i"`B!k^s9~)gf0MqFI JI9u/=1{}0SyMbQHΚK=lCN?A{ ǣnwroV+<4mӦS3{$JigA>-A2TPؕsU U˨ld &nrnk+Vx|ݝN~덜Sqt&ot0B!4۫9:)G6)ǔNJ&!` epEkU ,pByJ\Svѱy4ZgR(OiW+|i1ؙ&C W XcJ o㠽o-v\g^dYzʁb Ş{>&ׁH#*+^.OW֒g /N畮'[T%<^`<ʮR#3 `mr|苠+)-IҼt򇧿d9o~O/Px MaK?v2[#୾v~YtTՉm c 29@f==a9z8m@JD׃:j^a՞^F̢%nxTv{`q^] 2Tyr~ n~쬺fKYӇF[{m8:6NΟXXLNx; 7 2ڣz .sMݖ(ZDo}VϋыL_JcY8a6ԧ_Օ#][B]y YMV!]ŽǛl䵕SGJm IH!-Uz R^^oȘb-'M]79~M9;̺xhW{ڏњJv0 .kxz_d<6&~/m j*L)K~ ] q?.ZeDѫe>JX j)Ub|@d3ڜ5S^!ZrL&wMz4( x*{G/:6O+B'蛼q$j6eǰ@pxklB%B.J!LUmw5mOѳ0-#Nb֫0pI0*l6씱We&\l*,|eޭ %Kd]u5zdG?K )8a:#byD2XX> f޴RL n.+;JD6f//h:c= z]LM> Zbt2;,=5"?/ mL#brt kNjj̈)M1^u|λ$}ܡU֒*hkOc 4Kkko9;Õ@,&^CʊWh>YeHGԳO skZUuiYkw.o񡥡 ̱̉K})hqХ)=9 (/ڼҺN8ib<.)'c,*#::Kt ym"]fQ;Xwh'N^nY&i{]'GE԰!hdδ%fT)4UtFX/u[.;B}NUu;,b]yڭNY*7Fl8<.gvN&q<@2\_`tpFU} QNS>X̋Z˹N*J+<~aݼnְ?WkI>7<|&{n۹EY2g5O9GIbBhY 8(ad{NVp@!R7<fI;Կ~Ҏ3-D,n8(쎰/w X"U౟vqD6ul4]aM~F5ƿYQ>gtuݔ8hi$%HթQH~d_bˣ0= '"L3Mw72odVW)fFckWI[7ӊ#ڒ@]aˣA#6Z.g< 8,dx1i#eo.`C88Fp1꺁֯vs7K瘱C{(MP,Z Ϲؾ*B OnwdJX#F+-p"^H49kPfޞ^Vm.StSiija%:䮣v0Aih)wIK!;G/U00D ڶħp"PφWZJ9ژ{(U ޕ/O=0 r, 3oث)4l&Q(5q]kHEk?"kD?„ߛɖ<=>%8cR])+^\np[/McmǷ;L,m7 %άn')hbl'CGB>4KA) 6yL-Ey㐏[ %g}(9|x2ߖadto ^hH6(J(əV8mD#m#`BU~yJƺvb$pSEk碽{v*t휐d\V+<]!X7&m?(+|R<ټ_X}tZ:Wιp ]WwQ٤I+`!蒨n8 33dmZ Z|<̻^JM&k+L zp*1%|a1kʡ3< o798hu?+_-]2i;~OlK eT6:{9e]ʡ^$ MG"ϯUf IwJǭKʫ?-qr 2ۚ|+=Ls6tI5}oZxZV95emRĚpYBKZTRxw(9 XO^+2{6.P`VT2Rqӽ7[Sfro?,w q 8hJZu_,TM÷)6~1D;^?j$g_~!"F6"W/])/UޛAo],d;PKm[CɛPB,>}nV6Jڸ-4ev_0;Oݹq~*9EĆ]h0m&g'wߐmZI߮e_Zk&gWfpq ڸt:c/X)YRB?T{sʻ*3''!!C)0ꂠ.iw[3E|}A<W竪fT)'X|3,2,IYkMVF)hMXl]-꜕<zݠLgT? 1)6X ódhujrɖ$́AOϒ?^Q^qZ6A1p> <8k`; 8p@YWbHyUfyr(%'Z~bpT;3=y7R$ToJXn8NsMn^FKLL芪zƓ=&{h[RirX 82Sv(5q+[B2c]o1[JX f3$R)ENy!hqF}BȬ8%(%ϡ僠,my-N/=yc|z83:oP%sRGz2p!u`]nʯv.sSfoBj1yV+ u$Exn%k;f%ɼʧI6-h~cڱԑȴt:y7scʙ&Bi'^wO$DOz\ugh̵(|2]TELWlcZ?HӼ޸,ޠAF2ڻNn_WM6sCiBm<4{6p83-M^u(?V4.OpvLe~ o[ʣ.$g[ ?2Ol*OJLffrlyVsnn[}j4) t).bY׏եg&ۘs3so[:s_& eK սS`JEp$l<_,_׷?;>3    00    00    00    00   000   000   000   000   000   000   000VIENDB`vkmark-2017.08+git20220909/data/textures/desktop-background-800x600.png000066400000000000000000000335001441550741700246210ustar00rootroot00000000000000PNG  IHDR XrBPLTE`;Z?[@]A^B_C`DgChDbHdIeJfKgLhMnLpMjQkRlSnToUpVwVpYqYxWsZt[u\v]}^xbzc{d|e}fڃf~gۄgօh׀l؁mڂnۄo܊o׋p،qڇuۈv܉w݊xދyߑyےzܓ{ݔ|ޕ}ߏߐኌۋ݌ލߎᐒ⑓㒔ߔᖘ⡏㘚䚛娑ᛝ✞❟㞠䟡娙橚碤裥䤦奧櫟妨筢诣곣媬涥筯记鹩곫갲봬벴绮鴶꽱뷹츺µ𻽺ƶ뽿ºĻ¾żƽʾ˿ YC IDATxylU7R(a)6lrQdp>((PI @%- @mD!ki?Ea]{̝3-tw3s޳j: &BA @ @ A @ A @  A @ A @ AA @  @ A A @ @ A @ BA @ @V+~ܾ닿Xz?oB!m^ڥh3#mIU? 6gAyoΞM@8_8%Oھ+.Qc KX8،ErKd1^+ԑaYH~Egw Gzf= rh+ *ڂW&+?h s/r97y:W֒ŐmЊ r~Do2yjC%CJ(Wcfݪ~.-sQK^;xⱭP2"J ry(=ٮdߝ}4{x4:\uhNhvqTlUVRѸوw5 \͛*vg}Lnd" rkc^zrf?WP;*L}P6dj*s֫ o4EH>A~*ij$"5}4uXD.ATIȟgyM%&\DC^ ;34vu<\UU DF;tAyU-ۗ.Md?jyr91۫& )s,M ά V'OS62XSL^L G&zU?l~h'G#O)w18^Ӕ6d%dR;Ʈ!j<$I(4nNjי;5A+rA?=?R0b Oٵ0de DuRsAori u\#*;5б*A\hAy''=ekkOm+ F/M[$-{aūVwwM[j¹MJ'lSvbͅʔcEJGcaSoo^|DtRq)eN':ђ~k9HZ_җgp=SfWV|ښ^!EG=pz<; ^nr |_<96!]tA^bmF}<3Uǿt,*lA&KE@J*45QO,;):E3,C+H9==0*&J̎c1\`S.kú͂'Qe rbe_b 57!YIa"OC ru;,>v=,;nҲϠI}7{\(U_ OySq@ jeV<"] =X[,tx1(e DNh]X^(A M@jEqXlU)s_FN!V ̹@7+UlR=wW?C?BM5bVVjnTulC7+w"~kކfL=.6Jj[lv< ⛖t@?rm<|^N>'jSL rN?O %ifNƒ EDo]D`/qEMbB]F$=˵}LHE66<\3wV-y-$x4q#9d!‚}F*VCb 6@AAnm$_Rh^#L]u7m dlrG{~ (ȟ 䍛,h<6f9٭6+b!1w'I=I/Z3 +Fa>YKc E'D9@:%0HY-#qu)YO"~ZphѭʖO!c6 ^UL\g2("2ئ/X YGZi;.$Tv;PN"=Y5/]LYoa_F؛Z Цan9e6}C [HGFW< ^NjBٍwMBK 'yrK]Ɓrf,Jznڻ۟E hl03<וz͑ w4A^C gw,2{.B *s.o$lxhpb=4B9q-O <7!3h\Ӝ (,v$ŭ!~^jx@)^OTdWzU =xb)I4+?IB߅UZi-C:^,.l/ >縠!)'#!]ܫsp *ܮ`y|YV!?p/Udh:=M&ȸk*,򐙎xe܂z-!)B ^ܫXD6J;5aIҚ:*AK+TzUhA+Z*etS>cCe)ԧ+"p Bڢp/ ;Yɛt, >l)ȟy܂WX x/_0p dz]i.yP'`>A.s ^d!ޜCmt* T/sC?{ƱTͅAҽ*eG#WhL>Ѐ"AFTІT&v: %š橆ACZx[*hUS?e ,?@8ǭYŰ&Y /*)wX߆sٻ(`( Jի , ٨X(y!C?B NZ{ZoJ#C^АMɐ{yPY6K]K-c<ُlT~\'`~?fDu7[qw|e;y̵A3BY2㑁 B|`Ug֏O 7!Y/C]eiѿ{LȐOYҼb]x ;h|X5Vĥ6_s2 U_ ,!Ag_ Y=^sw!ʟQMFV8tM ^Af˞' {FK#95~-jA􍢆o Z4ղ* Z-/IaCZ% #o q-WwlW-O}8I8`s[l _jP¿> +0l53wW_3R*\Gfji8DA#❽Ex_ TL,Hڡ&~Xᚗ 9&%+йyx.oc2SQݖ>9bT3?[J/YVy2y$sۘ\rZ6_2.[,y|#+H:,񕼜f2A.~dC~QZ.߬(]_*7O3OvU&[ 3S-p\ЎԉY--w܎4yu-~u4wȞ6on+)ۨʓ ٸqǩMql}f[iHI<}-~W_[R8oXWJJ o "[8$E2Ms 8:AnW[Ǿ-=2A/) /vt`Y)H /)t9vK=<$R*łgxbyw+) Ѯ0(GU= d[ g*`=9(l[$CR7'I{l UHG*y#v22UI?(i~LvE[$QW9);%gcQ|=Do;?(eY=?+-*ڂo_&awtb7ra*/YowfkOZ5׀AE=X [Z5PMV An+c%">.})Cv 9&Grd9v=:)_=L=^sل+~D„I%<+mwH%-"A!IDAT6ɏsAoP4ϟKsǹ|gT6'AVG}5[.0kN4= &=qYqҷpyoh~x:罘ӽCbul>w$=on?IDGY#Kcpk-7)HqK3{ \9gN?9휬hPsp-m8YoOt'oc?/ԽT^tJD@ԡ3.% 9&j;d_ͽ1 O=~&;Mi6_1:7-ȥf_מwz,4}Z/gtA藘}|}41eN^'Dח,'?cCO\n6W[gM&.]~ҕGB] m}7d")Ʋ3>% g*T3q~EuMd&u$ه:/0_W,A.0/Ӵt@X/ bWG m)Ȋ?\Gi8{liZ!C>&AJI0$=gcMZ&(H1gzNk\lv2Ѯh|)eZ!7,;) ZP$1^sF{3- 0 :_&Nn 1]}AFSC܂Ҿ 3i%_-b>HD7^upRhq&Hy̳53F̸n/yAǘ;9NEjʘLT?iF/L>9/y%18kCzf}~/c9`% čǍ嫔/ߐ:0<.r%%H |Z@XVrA&󎽆=aM4 5\?7<? pksc1Yt$ \ GՋoBHg;lA_Q{JtA^bc!GD ̲98TӉ(l3 Bh6ϹˀOb<.0Pvw BwM@;`tABs̱CaEyxơAΊ801Bi?b&:PA[)q*tAIۅo̩&A~P2{`/m9 ibOySQc:'`ulX(<'ׂm,=ezǙ-Oꝵa6sa[APtMQ_iyvLYc$C KLL H'\jx$m͐|<6zlߔ>!As8/32.u`q"%#"˸:]z,%q-ČWƧS+s13JI`>1-o ;[t,q󛇵g:'Ix֥0qŹ ܡE9w*r阧j.' ;G20:@$%=-. O3qCq$ |^u}CF->(v_ˈ\"%`2m)#}_1=wmŴۗ1!ʢ>|3S6  UfmKyx[ѧdm|L9GZD0{%lA|UGN_/~>=d94AB8PLCA$lȎsܭȑAY#y>b[-ͩ& MYRГ̳Jt8HYOۻF&HxzˬX*)HZ%)?'~C($9Z ęs$~l.z=!0G?Y5A{y'HNS*ɠX#63q~X$ ";,J|Wy bl9"E~Ao0G$eh+HCIjޤ@(Ξ]DA7ݸ"fqmfj1u} ByL|buyNļ_w9q$ rfMh).]^1A=+HCIc̏ę(!jȂxZ;6qq- ϸg5-H;sT3)~-/#=;,dAnOD3Y>kXj@uCoENX1V`i> MHӾL>%_~ow%aZK8q//Y[ZX.OD_"d>6#WX:buAs,/E+'x> چM{8һAoVֈ]i]c7¹'9Ď3 :oL. O!] 5ۦt-%} R7@ ⺈h) Wـ{S2N/:l\7ľ{&1@A @ AA @  @ A A @ @ A @A @  A @ A @ AA @ A @ A <]daIENDB`vkmark-2017.08+git20220909/data/textures/desktop-window.png000066400000000000000000001307401441550741700230720ustar00rootroot00000000000000PNG  IHDRxsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleTuxQ IDATxyWyoU;i4f4dm6yk b%\^'$$ 8\&Yqpc&&[dYemzGujzzfz<垙:=u{QP$B!u9gW,\m s3w4PKX6)xF}~^ڰ<_ m҆RhC6, iThB!VIB:$ BQ$B!$B!D@!C!uH!I B!IB:Yг VVQZ_$KKU_B!&(Z;_u~!BQ\̢s%#܈Q5/BjaMlәyx }!2 B!IB:C Qy.0y_ sC2: ~{C LL%[ubnnBgߨ3WB0_MdB!Dy$Y ޣ(|߬}q'B!" \n#VW (0lrOs&'jHB!&ID&W ^~g5N~wx=nCfBIP15 8 Gkܿ!bZkmdx'rIӣh< X)e_!'=Ų޽4md"B!IJтG oEB|HP oR!IJRa^_ORO!bX ʾQx(؏I <@ĕN|AB-π\'~y zb?i4G> ДLB- בȠb6r:7§u !b-7/x |k7*vaf5"B!%wB`'QM~M~IrmB! RØ(|m<4y<|Ļr$>F]IȰ@a7&t%]Q!X4˷t>j?*1d+1 *GrAtEcs%Hgcւ)Rt#ݹ6@|B!Z <^2p>#˽E o6p6'3˵?p:WΑ8!5<l%˛Lc)>W%|Ipoq`B8KFwϟ7x:c *O0I;&0y } zBA=| !`0INS!XBFN ƥ-ۅɛyS﷊f 6̒֟CB!(7B!$B!D@!C!uH!I B!IB:$ BQ<Ѣ3+Ͽ@Y":s:KwnB_ZkԾ ZІVDžR_,_?!B, V;w!be;xxB!hP!C!uH!I B!IB:$ BQ$B!$B!D@!C!uH!I B!IB:$ BQ$B!$B!D@!C!uH!I B!IB:$ BQ$B!$B!D@!C!uH!I B!IB:$ BQ$B!$B!D@!C!uH!I B!IB:u5B d=w !V(IX"@;r9k'Ip<$lI `+K>da ,*xx+aB,y`?bj{<@wH/a%!D$byZl s X2T # ˇCe]AX@B& K_ 8 ki.~ i ``Q/w>DfVZ6Hz& Kr{s3`M)B,"IX:ֺ!h=V/rG~H DyI/p7隶F:Qto*w X=6GM!jL:<)E4 @ut]4KZx73d !$B,> EzDQ^+fN2$NE?iuL! uC% x>xA!%Z7by<< |>2 Tx<>yihF</vHܣ$BT@!V/p~@n'>H$B84M8^UUQE7PUyTcr?+$B,۬|er)8ws\5=7U B IX8W`_x9!> X$NLdjj 05XO,gb%S kk݈Ţ(J7M a8=v̀ "kvOI!ƥnbr'v iI&ajj1&''dl6 ={0.%!!oXUF,&^7NJHJt9٬hw32L3v{ I D]T,6wwiJÝ i2̴#͒dH8MLT 2I Du꒮Kvw~*rق; 7*q0P H Du[Ԃ0Hfuo~0J 6a p KM!u#j0 ^P;軓*P VN(J% s&ul2Lp/5Ņ˝$BTZ7.{߮|ÚP!VI6֍4f ~v/Z:d`!f! ձ %;/OEm, DZ7֒$x|F<1b.Q9 X]F^STI DVE/\/UZFI Dj1'I$KfY$ru26ԺB,5Qu#ļήuXj$2-n(J/bMBI$nK$BTF-CnK$BTFzX5B$Rn( ֺB,Q\^d@YCu"ǃi(dZm[h4JSS`׋qFGG$NH D$BT* #z|hiN<'HdqL&bJcc#SNqa^|EߏdŅXJ4!( \R*@`0H(" FD"4448A"aq1E!H,UVׇ#kQZ\X%b@q"0u׋' D"b1B|>&''1 ]0ap /@"  Btuu$ccc<b_T%D!*vTdad2ΐ@("Fihh x V *Lr:D8 GCCHG255M ?_ R DV699^9744 |޽{чioo64MsKR|wyl߾`0d{w1?kuA!"I(߂$Ǐ3::ʱcp8Lkk+t:ݻNQضmlw :afyկ~ɓ'd2z)g /@[[~l6K&ĉUs"~,!*4) !:w$>ټy3O=T j0L&IӤRU puJSSXiD"av裏'x¹޾}hiiAQt]ĉ1509 X$BC@/fΝNPb?w0{쟅aG?Nw{>UU`4551::ZLz1/&R#CBoɓ\ve3=r_ٱcw7o.ӭ[Ƣ^''=)T*Ż.>Uq0dӦMmo+@5|sF@ @{{.}0+yg^UUg}qsVG"rX=& |S*i\q5AQy.Ç~ws+BWWׯ/3T@Qפ@9( ~Ɣ7MӸ y{{^mX=7|VZ,ۛOu.B*wu?=륯WHQ$|sRpgg.pa6nm挟8p^ý:_>EQzΊsǜ뮻A4FFF?8~zjE]GQ$|s&aLNRݻ Σ>:K.swz郃 9###tvv:-f3<3k~_k.g)P( g|_Ps9kUI[( I D-qwя~+2gk׮Y{-UU%JGGmmmtvv200ٰammm hI |k_sv(pf <3>_>UU+.j,$I DJJ/|aZ~)a̘JسgO?4=dttt:qk}٢VoƝwɮ] y'?b^LF QsJIFGGyp2g||K0$D|dY9Sls=Wtm+X7pr뭷EžKQywuxi{)ob3 :tݻwO:=؜I(TC]YQQ|/p{Yf8p ʷ?~xQ gsN+vb̗ՠ@2Q$|) p!'biOJ2ĉ:::f0 x<=3?P  닽'Q={L[ .ĉb^LF!*3o!SO=EWW?c㎒.z/'"#л"Q@8V2z'ة\EӴ˖-[Kg}4͒*h%B\R#De ӟtZmS1SF\X.9m0z^{cG :Mu;8cO3DY8Rv+5ܰ|ˏCϼ|E:\<0eY…!n \7ߋbG1~rr /4-[mV'ۻ߂Sà6*8vHa0 N`xgar쳒?_OwBn۶|#p kĞ F B\2 D0>>ί~+.ikG"|A>?᥿g1[r]-jE37Cc3h M+j+ x20G1 00b0Ϯ a3^ +w.dX НwY ID!*AN81 ?p# -҂fG jn eVD:cKa=;ͯج NoUoS vXQۥ@l"ɃOӅO9Hs HoM.[Qja9p5iڕ;US΃>XVuIzLJyþ}o~̃_-b Mf?P"?*~f~地5=5 T"='8r8#U\~$EO 3伯b!600}(G~gĚ +?k?`j7 xruӉY5 xOW<$$ IckmCFC;mfD3^97ְ !磐L xe&/>*ʚM,b@)bݜ+*Zjk #=`Zcٴ5.>xxiT% [+aPb7&afoLsO;9 09 ;V7,aV0UAh4J0dllTUgSHN!*>ם - @|*7 Sz(\Wus 7M̶Wvnm/SSp:_ںXv-x<<~NfQM@-b@H~k$i^$peC%~?>\H+#[ѯp/ ?Ux续چRd30v= ?SGɉD" XnWf|ԅ@I$y d%/W`U]`VOQp61u3s"ݻp~t3S0v ? ixU*B4n''(-@rYbЀs<&2) ZKGpmM EE;5-WoOCбon?߄_<j~.FKkiZSN#pUͷŽ$/xXf 5M!~?cccd2<_rXnIXw3$3d O^XgiU"87fsw3W ?K_/} ma@|+8 x;p8s=Wy3 CCC $( @.8 zzz8z(4c XJB,'QE@G'֮]Ghu&BEGU*g#хLLҜ޺wz| GOYbX;b-nTl*<5j|պ?©06( ~]vz^Y~qihA'OU`iLNn:fnhh@ǿ5mVBF{ƷBhY3:h5;K/3 / [uc֜ AIQt:YgUV/HwwaB }k:4CKn˄$BTǬvmwӀP6m}1Ɨ~뷫cLYE}XS5 `ϧ>S?aq l.^Տax<E!ɰqƲez} `Ν;stX6$:+4@ [+<t=nX !c>g7˨+ZA_Q΂^ 4nTҘOLT.η?9:*l&"l6˞={8ъ:D"5?\/S)b@um?{ ( Yjkia#ϱ >82|ﭕi<^hh3΀l-&4Ty~3x%k @Cl [@cֹ:dq 0 5oaa*پ};_J?<֭#+nBEQ页Ǐs_R:B,Q3]TUiUmhd_A&7Žs p(r Vr٬5H::‹`FkJb9܁?>aw'!-;wwizwbhM6qy.B|>_Q|ϗ%7J%N!cEQx<~t]GUULD;HmObV5BGu2xx[i0 C,j=vt@_/AcSenZ 0xb`5h6pW뺎ad24M0 e ۷N6l0a6E& L +$BTǜ4;b4Xua:K;!0$f_eVO]tq8LMxny}g^z:TTUVoVzZt:MCC\psOY}GhmmsinLɲ%% 1ow\cVĪ }ӝ`},A?DxTrO#x^x< id2g&@:-[<%\B[[ۜ=T#G{]K$BTNZ9!UU;^waX=ˉsyy4̍uNR`X 0Mг Ls x2HENtY|i>iJDAQ,s=\x`b߾}Şk'YX/hڵH8& :]64`O Jq} @WiZwL.O*'a|Zxq]W}pK~|=DfSHG-Nu݋'Nu'OO3::ZinK(D̹liN>l ֲ_c0t';i'ɟ}k\_g d#LںO%U HNYA"l-a|>ߴ  fd2gGL&CKK 6r{ٽ{7k׮ ]^åGn ! I]םqMr%nIzدc_4SV !+pۻ j\ۮ!pܔY+gN}Ʉ50i8$oeզ7;}wo'y<<Sdhmmell믿,mttGyĹQ.@!*"xx~=% aѯcNg!A{ S8=ޟI[uoD\A]u@8vwO쯋Ds gY&&&ؾ};/B</\I8*=Ʉ%I̜wpzNw`{{ܒ4qwz۴Xwv33c3d:7ͻ;3i:g'RUh4iLMMqIַ|s.KgSbْ@^d!}lN{EZDp筤mcϐN]N8tu,#{z?Z's?Dmm>g撀ٸqc1.(ִ97b@mZ{X.83?U?$$;͕os&:0t_!_i=wZn=_PMLWW'N[os\UQcn@hb^hoO{t]VOUUJ%8sC$j-Fݙ\ ۈy!4Omޯ6GWWccc 388޽{Zeb s"D$ 5( MMMYCqFvڵ-\ | (H,CQbH$B,#M34P(؉,{^w~:{1{= 1~b6٩MXjp K-kZn@ҴSؿN 3za2{23tK @Q`jwR ^;v/zq!E,$R S) ===Y_Ӧ*tệ Ň<%,V_1EAڥho_^bP& Ż˷h7|3CCCy( `p^!z?@:z*YܧZ~?k֬\vew}nR"?k!VB,xxL___Qwv=@~gNN{ܛlSҿ sWpϘ-FfeBb~VJ(?5\C[[[cmqŀAu{8}{wo绔?X N `rrd2s=W&S>-\v"l,D^[ 6p5װjժ)B N;{{ =?W @qZ#122Ν;~P\S1IۙRxZ&W4p/ ThNUTU:m; b.2 ځ`=)qW[v0u 7*TݟR6{*d:桇us Q~ "bM@I]w;v`hhEO+n}!wbm`)TAQ&,ֺ!Bfb(Xϭ+-[\T9{k!Xַ0CCCs9Kq]Y nH L#w͛7Y|iu{ 0 :ˡ˿K?W]uRN^|HԺ!B@wݜq[~ߙ p'`9uϦ^/L͙Mkà "b47b mΝlڴPF@wٟm.Vb444W_]g#֍"$BX0TW^MCCPH$ɍRYnPI9.bA!Ģ@ԋՔɏΝ;D"A~]̳_Na[nM)=#%\놈" ^z* & vRܼf"XF%FRl۶P(䬴'wKgƍs9nJ9n((ƍMfQQAnZ7\QƦTBC0+*t/ IDATx^'u] 4MggC0Mt Blڴh4Ģ}*рEp 7l؀iN`J$ 8^WJP(л0 fb0Mذaox \ k"V6IJ8ғJww7`'''sn؏o3b洟({<,chnnfǎ5nX$+Q'vRD 02+vd2ӂad2t]=AKKˌ |>6mbΝ<+k/"CbH VKh5N444ih:&J:u K4zd"H:%-rfIx[vfd2 p. `q(ӒC*riEQo\ @#֟{j2I VNjHUUPUIjwa:&].׋R)x饗0MOWWN2$JL&"N;9Fihh  Vm`g2}~`ww'v $vf1MMhnn$A6oLSSUh;PZ7D<*JWW@`ݧ}Gkx<3P՚X,$d {a޽Nb_-( PZZZK^&!N;?NNߍm'ao.c@ @:vj4MtͧiqӟtU?"R dkH V3jl``:wZ;xIy]׉bB8rŅ( >.߾1JM:?1p}_IgpfIپ}gj.q; !+/p3)E]D(r=i3'cٚUmH@Q0`0HR3Ckk+7ne(p{v? {f(D"WƮ}8vO=TUY#eXnIJqԿbƍN} ǰJ)B .d2<f|q"EQm'9v)!kA,?rQǁeە! IĊфzZ7lի|Nz c4J|>N4MYf͌"f'~@ vK' `@ @(" 9r>+( ^׹.X=#>'O ňFNOZ9KnXn+T߹|p@ |_i=zikksvhՋJiqBa b,Ǐw; Q~j{FPUSN1::DZJ\8U놈Ozr׍oՄB!=\վc BD""px# 9,"AiGJ^ɝD?o'[^bNL `tt]vyD"Ass3---455JSS^YT5.e#IJ'CbEJj֬Yڵk DQ"-l?644L;ihh 9r؃}~ϊղ{x~]s0_@6%H8D"N] 0u]ȑ#LMMH  D8vz/j8 ժbٓ@,{T/hly#g}~t A~$@!&8GM^vs`XoHBL 1; tOUQJjI_R4]{FocrjT+s-*0 \.yJdHFzye7J3xB$ BN; SoyP<6spC1<<x yhTU ŀ?^7b@uKlT, $ Xb@b&'O$NL&bpp\.G&iO#$pNt=f&v\~߿a",R THA ߥxKv$ϣi7>X&7|TY8HMӈF$IL$Nw"$0<t4)Aѓ(i^]kq=/ ccc^>ؘ'>"͗A|>O0 /~qU9 Wa ;6 A* T%=Ie% XE.{Q\}LMM199X,\]Dl7,By }}}D"R=ЪeqqB#m{] ,TYb(I.Ϊh47P(T%/y ؘV}ľP(F DQ U{I4MH$ ¤͉#8sFP@ѓ@ eA"lSn[uV&''Pn@&U$e0k1{Y133lwzȸ`Yw,m,L?δ =N`J]fwgU(X96WU 9󘜜dӦM300z!X9t3zb4M昞P(e^:q'O$y&p5{xKrΒJؾ};_}Sɓ,,,P(744K_Ҫϕ~! O>& ۑo(z%`Ik>]u &^64CVTA E7_ˡ.###8p={տ!FFFk0XXX >lvծ+: o߾/R)CrIOB!QSgbcSF 6)lf~~7~7سgO( BvdTk/0+̪\ %ƒտNl_>vݻB(l~٬͇$[ظ(&4eUi+?Mz_̖-[pEB\|- 6z@%*|(&vK e_u۶ٙMٴiZ+ViuY-ٷ,GF"OvjP@],qkźS8͛jGb"h3330@ЀfF]B*&]׽M^M$ɐkK/ejjJ5Qx<ݻBi@I䶱GEņB EX⫗S4Möm,0 /Z !۷oWŚ 'Y0dxx]SZN&iGxEFGG[_)x6 Pl`z u.y(]w$Vk[),,,Jj;PX,Vu4Zr N sB E' Q#-?/fdd U-cqqT*E:h-@- /E74J(:@h~oFYt-lȺ'gg2%:UkX,z!0*6(J(:\Q뗲O^e_:|Ҝt/,,f{2BsZy*`$ErtD"oud۶ Ǿ}v䠣t:nz!DKJ5jn;@y \Y뗚 Hx.cttt],dY P388}J!8U:p*6J(:@egddii۷og|||ݔeYRT !5UFC ԉ7VtP(py'H SMӘ`xxW˶md<uVJv`pSoJ(* 4ݻ#Qq>ǶuQ4Z)eQ(\tAQWE;I{ie x%˙XRv;+uBPhpr9  vٓueQL‘e ٳvTolBt]W"A&&&O鱒ޞn (prOupp8ܐQlIV'i PPܕ@*WO0$LUrP(K4M .!d2I,#N5(ڋVq%+t) \=27oL<_7FRƃ"nzVp8|t Jl0Ppj^ Z#~mF$Y 7/T@iq"tT*Ք6%6ʢhYE~&~f= #C>d[f Viٮ +P@ .\͋?4Vº{%S?(G3=۶m# Q,m>(Q4(V`x5/sѥ4 V24ԛB\x9AYd[浢:۶mC4N>ӧptMCy6J(վж%^˲iXoʹr.4KߏitC `?*t4Bs[!3deWք+4ôct]'sԩn\b=ٌŲNP0 $.T'@]HW~@@ `X `֭!6?@h/J(͚.X,b(-]ԚeYeFNFT>[l!b&T'Q (͚E i:SSKGK㸞<IA/oLZK  JqXsQ fSX"l\.G.xd|z@0 >m3>E{Q@ll6K&!ϓf, ]9s˞Wi0{wsljj`0?ϚtTMd@^P4g4=f y{ϓɂPJ176I2k $IΜ9ÓO>ɓ]ig\B^֗T o֎D"a @p]T֛{3]9rG0nMZJUh-J( <{VeYR)`p8L" L2::ʩS֕(H22'$l߾ B"'NqG'Aa``; fggYXX Nd0 e8ʝ+%|>7G8p_ݺZ^GVt۶}(`˖-B!Oʿ_0$BH$_zhh۶1 \.ӧ9ugΜiIw~PYFq3\~% ˂ ޥd2h4Ν;f^ M! tmfk5McΝ\r%DQ,"{a~~|>OX$sL)پP(0==M0dllP( BPD"-[HRxb 1# %jQꗂbd ߥR)iӦM,,,ts\uT5*yLdnnj~0d߾}ٳ۶azz'|ӧO7;>ʓO>I<'G__X4`0 dM6qiN<ɓ' N VpHD>`PA<//m,..P(i^0 !(:G:ѣ<- IDATeAv$|ӧO裏rԩUxX,CCC x;vc>ǎcnnU?^+z%g VگGaJ1˲ ͑fYXX H4cJ@e_E{mǏOϗ: !b``C5oeY kvvzP($;v  qA?|>}|I9QcX(hOs.ꯅHBx:0 B3gpFFFz (K\O^$Orw=><O*0 yx >2  *(+g(ڇi<<#?D"D" 333m3<<3|_gzzX,ƅ^͛ox(ro`@t{޽s'_נ&f #6/_# bddghV0MsT9<#mT(:' rrW" L&YXX {yxMBP4|ki tgfm\ P8q_zt]/|!=)BHMӼJ GvD9AbAGnf xr)33LD6e~~X,wiF i.Ei$cz] _$$,/vXjB!| b-|~~??СC :hω)Md un&G~=/ [*ax{-/b<{_CE&z 陦i6rǧ^TinK@9~8%ť8==[VR]w۷oGy b1/̶m%lŽ6 b=/v2q!z衞HBx z⸻?y[gUҤTwAq743;ir92 t\.G6Ų, <904 }q!яxKm?\ %k7m{+5|tVChHp]Q?񶷽{キ'Vhx# AL@7rwow;A Ū{W ̏F 0RVǽY{׼5y]QU9 F^_V4at赀97:_y`gbemxp8ܕ6fczQgod'>?uYmۘ#M%W վMt ˩uP/ڹmԾrM&.y 8 ߣmۼ=O=T؅b1/DX%F!iWbw 84,.bwZv7F Ks iQ*%+ۙzy\19jn‘gEK"qAuS`#z뭼MoL{ljF*`FÀ+Lnݿmn*B.807 X]>*s1e1 |v@]U E~l&r\?ɏdAxDp_ۿquo|l{B 갌 N"%>ox}@ᇤRNC6 .."</;Eqk 䏈RM6J(ۣ#cN'h&Z\U]?@v &4Çs5pwNsM@&$c,yWh+pm 9R` 3NC../ ri\%^bek[61<8ãX|niK襊e7 dfff&z 4&,iANVCwu̟aِZL1Ţ+"_{C$ ߏ/BD4\ή!?PrirB8 ]Ca67tr O=TϴVTǶmgM0n" woN=́BpSt wCUkಋ`6.{GV#&G ۧ20C08 o_n-@n`зվx%?\[ٟ7x#w(0 =uv )r;y57HAt3U"5p~ؼ"1g`e]ar fX|%U ~Z~g8AH9 _m@Nj^ Bk^wk;Y\\욐>mfevvS1/x9w$E~\+4zΙ+vv5+.q$Wv<؋HU GDZQ@CxAHB-xeu򷙞WWۘ~r `qqYΜ9Ù3gX64h1|fۅlY 1ZAuå;:!(|z8k; 9^8MS`͹?Sdq"uo~u*6B ɶ/ 3A,?d>_[^7{ NLv__}X;[>_zK`iER-..JȤ 䍢cН ʩj&pZ]kcAG[&Ѽp>E[w^|'\\r M8!׸_Hc}7m١SVANUoԕl %³l{aSGB1~綺sWK[)빫7B '?Cwq饗.X,NIRiti0Nf.U_ ks h88֮rxYO:ӌ/BͰ'弰,w mżl0@$„. r QAD lm$ F]? ˋ|LpV.n1]wƟwW~#>Op!T^1 3B6m;pCSji#8ς'B %]`bd]A?^i'iY uoHXl ܿs L݉mtoC!'8x¹ԶsZQ_ ?>fVǎ~'?+^ ЗDP۶re[6-:l !XfKo3M] d9/shڏ'O8?kZǂ/8aFM'@X@@l@XG lj\wgƳGј V7hNAB@ja |7)d(vX|M7q1^W~Bճ#-X,R(ʭX,byGtr!Jߓ`LrX/'NݏGXEh,m/89b/[)אaȁDӔ`@sx( B&!vV0 !6ބbѱHҎ~*mr꠬g xG馛ˈkۡa,*3axbѻ,; "BP^.Vtc}`O3p|l)+4,;gY0~&8gM8 9ފp"}9 c֦mHH?y|^tY\:4Qr. ojVA߿twpB@T'9pG[nE/zjr_, 41 36Ӵr>mTCn!+?u=z4\bp몹kЃpqQsn#1G[Np.!}G>7%t;7WE@g7L; @ m@Wƾ翼N$s]FO=7x#GyK_U`m4m˲jcMݯD@/cY=Gj t 3|V( dAz@4seɉ;J= ?>R:J!ƫaĤc+s79O,|S;$7MX&nobKW۶[8r~ˉT!RHKm<7H" =BtO!@Ex\`>cr@5[~a&]XќAzX:~ZW`c@rc7ms.Av {BNPINBAyG~'xn=y&R!gMB`W7{r@#y>](w wn``I2|R{b1"w^o{9rZje*b$owDSn\K;O?ͣQ5Po'0w(TiϚF7 qapA3<:P1r~ϑ['x,E@͠EYN3!m'F2MV( %zx`_ ?Gf*j{+7$EnD}Bܮ3>1~4?=~a5ZH#i# , hBx'(:n E@0P\nƾke^,{\v4vϗ']}%PXA`-ኀ8h7Pt*`/{bq炱'5B12fM$?q/B8l0.B;Tk~^= 8p@5 j~ MӰVVy~%@G (%IlALeWfF]/ qBeǩmw]@0Wd&8{©(Ϊ>r5(ϫe *~'J .u SQmᘦ>-D@sF"{L2oZijF mwkW\Ȥ?vSQd}JlY(߯-n8%t om͡xkT s"jO117F/WW^2}]i_ ܑI@!_M PIX}>pF(jwK_)32+t-U]uɺ}o[a`-?-Q @$EmDn}" L.ׂ%*́tK տO+G$P2 pĭ p+ :hGAёK hc Q`,E`P4 d[19,HV-Eњ#ѣ,,, 63ב0ܞ,_/yd|_VlvAK6J T"`EP01 yBT;cA51j7_ߺCe~~+Tj4M,r/Yq钷-'[_&v?!7IB=  w8b-(#X|J;kN6 ^( J,!0_n:uyf7(!7 4ضR~w RnOW̔fAh\_x/R%zL\!|Nr[gZU.EmFBCpͱcXXXP`T&ZiIP@ٲ{'jV !@>'}"Rn90fXBvCYVVS2~VorQ鴚_ ll6m8.<)4JbU r*Il n!)V=8V(@>W/niYպohDM'~ɇ!V4)yrLT*E>[%7Wz gG"tu6A82&?փUT@ P(Iȩ\[~uE4-e+֌m۞d2iqU,(Vih56Qg ɾsUH遳J9 wpVvz}7<"P z1B%@P J1??ϙ3g8q(_ꑭ{WNiLvi#b$7i>)7^yj'衙Bqa?y>k;}X(#&.ukmJP,EX'_ݜw߽{7XL^#md?~n\.& U><1j?R$T$sP\ovθbm M\CAeo !,mnV`67k'/] H$D5R,Ld IDATazz3 u+ =+Zk8os (mݢ RT-!t@D}}'7P0-lٽEK^,gbqWoxغu+CCC*az-qQ=g?Yfffߍmwe]V;(X^eyrMX|"͍ >  㠍lOuCpqcac9LNS暈EЉI2J!໷A\|żd߾}8pI%րa<'?[oɓe{F_"@N["c{^.wr*%/b@w߇,lb1Cw}amtէ0LLBk"ȮU.P[8u300UW]ٶm7ofǎFWL&+V~x_2_M_ A*y{*t@*e˾N2!Lö 6~}P:2)&OO$"5LLLp9gahh&&&$H*Vi}ݼMqSg)3VJ#T]ubފ?D :}H%zA2V/@^@tu$ 188HYhmCq5m4XT ,%oozwCXLcigQ()\߁L<^w[x>OK.a߾}D"t]W+q1g/>ky櫢ަk#"Jt%e{\\7Hofvqlk_+z&@+ǿwPmPϫQI=D$ AWZ^>O%V? Y\`Y.a)N8[fxxvܩ*VeY.z_)5ZCT~gQM_*I@p'vh6:J0"pVK*\pö`!`vǷH&$ TR IR ڋs<3 GrYf"i,+cc(ݔˆZHyЕrx_~t /u mrwr}к]g?AĪ>_Jiv@F<݌#G{P-WOE Y.  u{7d.HfǶmN:5P 2+~D%(V V YK^1oH<݊܆bDF PwX*]׊34&Ba5O}|G*) mJ⋛b|+}e2ENq9c5|GpP־OԖ,Dd]Z41W0LnO<O?4Vc=m 7@"O=v ׎{?SXi}ʃuۚo>OrgJQA_";Keb}c[@" #py/~}.9.Ķm"|STKoːo_ WyX `0wf|׭(y*-KU/hVŦh N'k%C6m/huii~|/-ϠC __RPumR[;, ?bKQ%:K=_rp 6QRB߸q` $C'F9paP(8y$_x{t_6Zb+Kd򨮵ձo6skGXe!:Gl .wk3>!J j GܜAx%wT*ų,dضad2?Σ>}ǟvAVTkC@ꟊ/v ,s_'N1S4a?k@wc-"e}E?iR!¶fy"~%8eaYBT*ѣGyxᇹںگd_* Jߋ_4%Y[`^kL=>Q)A SiY@O4l]p^e[89ֶʔ@?ifK!JPb}S?+8& vGm,0 o~qE@hW~] =u]Q ap?_, ΅4 O ?}r뮻뮻啯|%g֭188h`0H (lƶm4mc&|t:,,,ݟgBH@w2zCYwKc9ҋ~s#ǘ'UWtJyOZqٮ(5݄J/{;0"Xm;"JJ/ם:u89x 7ofxx!8P@ PIoAPX, /C?0??ϙ3gp}q}yN4%300@2d``~bs3??"L\.GX< OFh㞉z`q2@b)8 x!O Jq6Ni4ϰZ&oד(\:fI_4S]pO2MT*E*ĉp05P~)%:vg!`P*6]7\")9 N~,ߎ1F Qh5Lgמ4T<A/ꛔ䵚_ov*Ŏ״H_&\t \ce@Q~ϕZFN*Oej`ڗEv2H-Yȿ~F\W.G R_% {eUHT<nfKE- FmaPE !OAEdT5^9vߤ:6{IY?n6B\ OV@ӧak78Fy4Laaiw7+/@@{X@:MfֵN K&I0\y? H* ]+=iamod?O_ln4E_sroL\',mgmqzFumP,X,.8Fa83f>)PdF^yz%ZG#KeE]~U2rU]AQ72dbOmyL^=8uӀ͠/"p̶2Uo,4J 7QDTu/]]~ks_c]`&ٌMjR89_4'_y%GC+jmM$ j?(o.lvǍZK\yf˝kkPGKvzI.M"4@mp+bI%pi|5W]|ƿ i-l2-2iH ,.8aS~'_0wJU~W_~w ~W%zP) Pq}Nŀ2I*?S(JHjW=5ӄ@@UA/w/Kh'پx}劾r_y瘾יضE>4d2cS0}Gβ 2%ZC(Xs7)[y_^oe2A8-ypdAx L$sdw[ ۆbYU 2?F?v)Pם[ptݗFMsPPj]-G{}w۶i䲐A6kXú|j6* C(:ֲ_rO=gmbK%#So&Vroqo+Ӛ?Z$Ë*E@e(Rc>/Whz)dڻ8uϐHJE)JF^v]g7)H/@pl p $0 ekʖlfɺ[EJe2tꮾ îL99uڜx o@TrzdJX&>D7Kk6 Mi[?\ˋF=ge2&QEXZ$I& {2PB&g ~u=4-ch:P_hmb, s,}{.{+LEQ!G/տ|!80RM2ڴ]x']mSS2ߟIPlH8B_@9N7D@L3s}/`ڎ|]w)iZ#i(Y|ߓ3?p5D"D#onu5ׁ"Z b -s)L=7Wa/%1xS|'+0AyTaiٳb"!q?/H$ = lp@z406& ""7@`0GL~tq&U`S¶Z$j_UsE6rlJD^kkdaEbo79'BzII-؇c{[{h-6U*1y%coW$ kd@[2G}UJ*2 ?絍z;Ҿ ›^ u& ?0֢Uhdw+^DrdFU CLPYh wk)%sK~ @ye;}:n[<kz 0cth 6UaGx?%V&ī'?0ҰEe2@@SE 6X,* 'M o&JC]Cf)u/' l0`fg4 Ll +am=O$Yh/$~{B%,$&4/IRi7+~>iu' :e& 1/̳/޽ǘ>[ {blalٵ #ƶaR0_V"_!2KA ,}"Vܞ He#OR ioZ[et7 dE-"z]O\}r .u"Ch[.B 8>!tlKoR~?bS\|^;,(*c8*-ӏ07❇iy7LG ܩq^ƣx*kM?axbCزm#V?-ߓW (H@" !h6Fá_ zr@Z}z8u3r+\p <=H!gꤑ7w>)HPEsuohWc X ?oexy8)!&Q$!OAR(5 1_pcw>\h[B @oޠ1}yɛ{&e XNl=c̗ IDAT7ck_4!PHr9 9_@t=hm+my(Hn ʿ gxn ԊSm~S[)qXu BLqE#Yd0ѷ6O DO0TܙjB(7;nht`e`P,F\ *ĺ;xrgO:mFmZ#FO!0H V[R9X(lH#?ഇO^1# dI%5~E nvzk98C,Fr]_չuDDft0h|wwm:?8ҏO t-۟9rQHA H>sz j(\H#t2iɁgIa;.3w0s8q݅clGׯp402=PNTDnW.91ԋU)rՒPoe\$yP[7 P'xk\i %"߲I?@ (eG|3_~A5(h-uY\^6 UkR.`0)0(IդŠmű:n-%Rk}#pdFoFlT[ 4/rPPBH ȉtF@]?,/W&q,5NI̽=sS#*"(.3&g1w~ n,QȊU2 $nex:0^1_]mFM%`5Y[!b ^Kk08n^u7DnP (:Őٚ, $'o! YPO=5MbQVux.uWѮB\oOo.ۆ( c@t?r^:'E48>8-f,y R&]Ox0ʁo~Yڋ:&M[ھi۰ 6>9{W?nH@I\؁R4 'E.Nƫx ~։瘗HhչGxvV*>i#;<Ei??!. |&hs~Ǭ8b[f]}Mc,-^;Wir5Ⱥz!($tB_(lh_}T@]CS~ L{,}Hy"EY>qe}aq4 q3'#O).hqgLcꝋ" " u ĵw7t LHI[>ElML˪}]IE*601l;m#k~/$|' (,|m: 1`&}j+ ahlGasF5n_@,? S:? |)!/e({S`*"=7SC06܈M}kpFӬ;pn=7EZcY}@a)T3hKIO\}z ]5[ӏ~ o}`Qw?^ֶ\}S)Z^&lx!.NSĵSI PQWCS'繷3O0?!ꔹI{eȆ~׼Ci8.;Ds꾒d;-/E6 LMc2&ǩu$)s/x$DuE)vU]Q[*.rS$LdDD:\3ʝ<2C?R'=0.45' ) root_incdir = include_directories('.') ws_dir = join_paths([get_option('prefix'), get_option('libdir'), 'vkmark']) data_dir = join_paths([get_option('prefix'), get_option('datadir'), 'vkmark']) add_global_arguments('-DVKMARK_WINDOW_SYSTEM_DIR="@0@"'.format(ws_dir), language : 'cpp') add_global_arguments('-DVKMARK_DATA_DIR="@0@"'.format(data_dir), language : 'cpp') add_global_arguments('-DVKMARK_VERSION_STR="@0@"'.format(meson.project_version()), language : 'cpp') cpp = meson.get_compiler('cpp') vulkan_dep = dependency('vulkan') dl_dep = cpp.find_library('dl') glm_dep = dependency('glm', required: false) if not glm_dep.found() and not cpp.has_header('glm/glm.hpp') error('Failed to find glm') endif assimp_dep = dependency('assimp') xcb_dep = dependency('xcb', required : get_option('xcb') == 'true') xcb_icccm_dep = dependency('xcb-icccm', required : get_option('xcb') == 'true') wayland_client_dep = dependency('wayland-client', required : get_option('wayland') == 'true') wayland_protocols_dep = dependency('wayland-protocols', version : '>= 1.12', required : get_option('wayland') == 'true') wayland_scanner_dep = dependency('wayland-scanner', required : get_option('wayland') == 'true') libdrm_dep = dependency('libdrm', required : get_option('kms') == 'true') gbm_dep = dependency('gbm', required : get_option('kms') == 'true') build_xcb_ws = xcb_dep.found() and xcb_icccm_dep.found() and get_option('xcb') != 'false' build_wayland_ws = (wayland_client_dep.found() and wayland_protocols_dep.found() and wayland_scanner_dep.found() and get_option('wayland') != 'false') build_kms_ws = libdrm_dep.found() and gbm_dep.found() and get_option('kms') != 'false' if not build_xcb_ws and not build_wayland_ws and not build_kms_ws error('vkmark needs at least one winsys to work - xcb, wayland or kms') endif subdir('src') subdir('data') subdir('tests') subdir('doc') msg = 'Building with support for the following window systems: ' if build_wayland_ws msg += 'wayland ' endif if build_xcb_ws msg += 'xcb ' endif if build_kms_ws msg += 'kms ' endif message(msg) vkmark-2017.08+git20220909/meson_options.txt000066400000000000000000000003751441550741700202470ustar00rootroot00000000000000option('xcb', type : 'combo', choices : ['auto', 'true', 'false'], value : 'auto') option('wayland', type : 'combo', choices : ['auto', 'true', 'false'], value : 'auto') option('kms', type : 'combo', choices : ['auto', 'true', 'false'], value : 'auto') vkmark-2017.08+git20220909/src/000077500000000000000000000000001441550741700153745ustar00rootroot00000000000000vkmark-2017.08+git20220909/src/benchmark.cpp000066400000000000000000000036111441550741700200330ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "benchmark.h" #include "scene.h" #include "log.h" #include "util.h" Benchmark::Benchmark(Scene& scene, std::vector const& options) : scene_{scene}, options{options} { } Scene& Benchmark::prepare_scene() { scene_.reset_options(); load_options(); return scene_; } void Benchmark::load_options() { for (auto const& option_pair : options) { if (!scene_.set_option(option_pair.first, option_pair.second)) { auto const opt_iter = scene_.options().find(option_pair.first); if (opt_iter == scene_.options().end()) { Log::warning("Scene '%s' doesn't accept option '%s'\n", scene_.name().c_str(), option_pair.first.c_str()); } else { Log::warning("Scene '%s' doesn't accept value '%s' for option '%s'\n", scene_.name().c_str(), option_pair.second.c_str(), option_pair.first.c_str()); } } } } vkmark-2017.08+git20220909/src/benchmark.h000066400000000000000000000022001441550741700174710ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include class Scene; class Benchmark { public: typedef std::pair OptionPair; Benchmark(Scene &scene, const std::vector &options); Scene& prepare_scene(); private: void load_options(); Scene& scene_; std::vector options; }; vkmark-2017.08+git20220909/src/benchmark_collection.cpp000066400000000000000000000053751441550741700222570ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "benchmark_collection.h" #include "benchmark.h" #include "scene_collection.h" #include "scene.h" #include "log.h" #include "util.h" namespace { std::string get_name_from_description(std::string const& s) { auto const elems = Util::split(s, ':'); return !elems.empty() ? elems[0] : ""; } std::vector get_options_from_description(std::string const& s) { std::vector options; auto const elems = Util::split(s, ':'); if (elems.empty()) return options; for (auto iter = elems.begin() + 1; iter != elems.end(); ++iter) { auto const opt = Util::split(*iter, '='); if (opt.size() == 2) options.emplace_back(opt[0], opt[1]); else Log::info("Warning: ignoring invalid option string '%s' " "in benchmark description\n", iter->c_str()); } return options; } } BenchmarkCollection::BenchmarkCollection(SceneCollection& scene_collection) : scene_collection{scene_collection}, contains_normal_scenes_{false} { } BenchmarkCollection::~BenchmarkCollection() = default; void BenchmarkCollection::add(std::vector const& benchmark_strings) { for (auto const& bstr : benchmark_strings) { auto const scene_name = get_name_from_description(bstr); auto const options = get_options_from_description(bstr); auto& scene = scene_collection.get_scene_by_name(scene_name); if (!scene.name().empty()) contains_normal_scenes_ = true; benchmarks_.push_back(std::make_unique(scene, options)); } } std::vector BenchmarkCollection::benchmarks() const { std::vector benchmarks_raw; for (auto const& b : benchmarks_) benchmarks_raw.push_back(b.get()); return benchmarks_raw; } bool BenchmarkCollection::contains_normal_scenes() const { return contains_normal_scenes_; } vkmark-2017.08+git20220909/src/benchmark_collection.h000066400000000000000000000024651441550741700217210ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include #include class Benchmark; class SceneCollection; class BenchmarkCollection { public: BenchmarkCollection(SceneCollection& scene_collection); ~BenchmarkCollection(); void add(std::vector const& benchmarks); std::vector benchmarks() const; bool contains_normal_scenes() const; private: SceneCollection& scene_collection; std::vector> benchmarks_; bool contains_normal_scenes_; }; vkmark-2017.08+git20220909/src/default_benchmarks.cpp000066400000000000000000000024651441550741700217300ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "default_benchmarks.h" std::vector DefaultBenchmarks::get() { return std::vector{ "vertex:device-local=true", "vertex:device-local=false", "texture:anisotropy=0", "texture:anisotropy=16", "shading:shading=gouraud", "shading:shading=blinn-phong-inf", "shading:shading=phong", "shading:shading=cel", "effect2d:kernel=edge", "effect2d:kernel=blur", "desktop", "cube", "clear" }; } vkmark-2017.08+git20220909/src/default_benchmarks.h000066400000000000000000000016341441550741700213720ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include namespace DefaultBenchmarks { std::vector get(); } vkmark-2017.08+git20220909/src/device_uuid.cpp000066400000000000000000000050361441550741700203710ustar00rootroot00000000000000/* * Copyright © 2021 vkmark developers * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . */ #include "device_uuid.h" #include static std::array decode_UUID(const std::array& bytes) { std::array representation{}; constexpr char characters[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; for (std::size_t i = 0; i < bytes.size(); ++i) { auto& byte = bytes[i]; representation[2 * i] = characters[byte / 16]; representation[2 * i + 1] = characters[byte % 16]; } return representation; } static std::array encode_UUID(const std::array& representation) { std::array bytes{}; auto&& decode_character = [](const char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; throw std::invalid_argument( std::string{ch} + "character found while parsing hexadecimal string!"); }; for (std::size_t i = 0; i < bytes.size(); ++i) { bytes[i] = decode_character(representation[2 * i]) * 16 + decode_character(representation[2 * i + 1]); } return bytes; } DeviceUUID::DeviceUUID(std::string const& representation) { std::array chars{}; if (representation.size() != chars.size()) throw std::invalid_argument("given UUID representation has wrong size!"); std::copy(representation.begin(), representation.end(), chars.begin()); raw = encode_UUID(chars); } std::array DeviceUUID::representation() const { std::array c_str{}; auto&& chars = decode_UUID(raw); std::copy(chars.begin(), chars.end(), c_str.begin()); return c_str; } vkmark-2017.08+git20220909/src/device_uuid.h000066400000000000000000000027371441550741700200430ustar00rootroot00000000000000/* * Copyright © 2021 vkmark developers * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . */ #pragma once #include #include #include #include struct DeviceUUID { std::array raw{}; DeviceUUID() = default; DeviceUUID(std::array const& bytes) : raw(bytes) {} DeviceUUID(std::string const& representation); DeviceUUID(const uint8_t bytes[]) // TODO: only used for githubs CI. delete when not needed anymore { std::copy(bytes, bytes + VK_UUID_SIZE, raw.data()); } operator std::array () const { return raw; } bool operator==(const DeviceUUID& other) const { return raw == other.raw; } std::array representation() const; }; vkmark-2017.08+git20220909/src/dynamic.list000066400000000000000000000000671441550741700177200ustar00rootroot00000000000000{ extern "C++" { Log::*; Options::*; }; }; vkmark-2017.08+git20220909/src/gen_format_map.py000066400000000000000000000027061441550741700207310ustar00rootroot00000000000000# Copyright © 2017 Collabora Ltd. # # This file is part of vkmark. # # vkmark is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation, either # version 2.1 of the License, or (at your option) any later version. # # vkmark is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with vkmark. If not, see . # # Authors: # Alexandros Frantzis import sys infile = sys.argv[1] outfile = sys.argv[2] in_formats = False with open(infile, 'r') as inf, open(outfile, 'w') as outf: print("#include ", file=outf); print("static std::unordered_map format_map = {", file=outf) for line in inf: line = line.strip() if in_formats: if line.startswith('e'): enum = line.split('=')[0].strip() print(' {"%s", vk::Format::%s},' % (enum[1:].upper(), enum), file=outf) if line.endswith('};'): break elif line.startswith('enum class Format'): in_formats = True print("};", file=outf) vkmark-2017.08+git20220909/src/log.cpp000066400000000000000000000111361441550741700166630ustar00rootroot00000000000000/* * Copyright © 2010-2012 Linaro Limited * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "log.h" #include #include #include #include #include #include #include std::string const Log::continuation_prefix{"\x10"}; std::string Log::appname; bool Log::do_debug{false}; namespace { std::string const terminal_color_normal{"\033[0m"}; std::string const terminal_color_red{"\033[1;31m"}; std::string const terminal_color_cyan{"\033[36m"}; std::string const terminal_color_yellow{"\033[33m"}; std::string const terminal_color_magenta{"\033[35m"}; std::string const empty; void print_prefixed_message( std::ostream& stream, std::string const& color, std::string const& prefix, std::string const& fmt, va_list ap) { va_list aq; /* Estimate message size */ va_copy(aq, ap); int msg_size = std::vsnprintf(NULL, 0, fmt.c_str(), aq); va_end(aq); /* Create the buffer to hold the message */ std::vector buf(msg_size + 1); /* Store the message in the buffer */ va_copy(aq, ap); std::vsnprintf(buf.data(), msg_size + 1, fmt.c_str(), aq); va_end(aq); /* * Print the message lines prefixed with the supplied prefix. * If the target stream is a terminal make the prefix colored. */ std::string line_prefix; if (!prefix.empty()) { static std::string const colon{": "}; std::string start_color; std::string end_color; if (!color.empty()) { start_color = color; end_color = terminal_color_normal; } line_prefix = start_color + prefix + end_color + colon; } std::string line; std::stringstream ss{buf.data()}; while(std::getline(ss, line)) { /* * If this line is a continuation of a previous log message * just print the line plainly. */ if (line[0] == Log::continuation_prefix[0]) { stream << line.c_str() + 1; } else { /* Normal line, emit the prefix. */ stream << line_prefix << line; } /* Only emit a newline if the original message has it. */ if (!(ss.rdstate() & std::stringstream::eofbit)) stream << std::endl; } } } void Log::init(std::string const& appname_, bool do_debug_) { appname = appname_; do_debug = do_debug_; } void Log::info(char const* fmt, ...) { static std::string const infoprefix{"Info"}; std::string const& prefix{do_debug ? infoprefix : empty}; va_list ap; va_start(ap, fmt); static std::string const& infocolor{isatty(fileno(stdout)) ? terminal_color_cyan : empty}; std::string const& color{do_debug ? infocolor : empty}; print_prefixed_message(std::cout, color, prefix, fmt, ap); va_end(ap); } void Log::debug(const char *fmt, ...) { static std::string const dbgprefix("Debug"); if (!do_debug) return; va_list ap; va_start(ap, fmt); static std::string const& dbgcolor{isatty(fileno(stdout)) ? terminal_color_yellow : empty}; print_prefixed_message(std::cout, dbgcolor, dbgprefix, fmt, ap); va_end(ap); } void Log::error(const char *fmt, ...) { static std::string const errprefix("Error"); va_list ap; va_start(ap, fmt); static std::string const& errcolor{isatty(fileno(stderr)) ? terminal_color_red : empty}; print_prefixed_message(std::cerr, errcolor, errprefix, fmt, ap); va_end(ap); } void Log::warning(const char *fmt, ...) { static std::string const warnprefix("Warning"); va_list ap; va_start(ap, fmt); static std::string const& warncolor{isatty(fileno(stderr)) ? terminal_color_magenta : empty}; print_prefixed_message(std::cerr, warncolor, warnprefix, fmt, ap); va_end(ap); } void Log::flush() { std::cout.flush(); std::cerr.flush(); } vkmark-2017.08+git20220909/src/log.h000066400000000000000000000024261441550741700163320ustar00rootroot00000000000000/* * Copyright © 2010-2012 Linaro Limited * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include class Log { public: static void init(std::string const& appname, bool do_debug = false); static void info(const char *fmt, ...); static void debug(const char *fmt, ...); static void error(const char *fmt, ...); static void warning(const char *fmt, ...); static void flush(); static std::string const continuation_prefix; private: static std::string appname; static bool do_debug; }; vkmark-2017.08+git20220909/src/main.cpp000066400000000000000000000104401441550741700170230ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "window_system.h" #include "window_system_loader.h" #include "vulkan_state.h" #include "device_uuid.h" #include "scene.h" #include "scene_collection.h" #include "benchmark_collection.h" #include "default_benchmarks.h" #include "options.h" #include "log.h" #include "util.h" #include "main_loop.h" #include "scenes/clear_scene.h" #include "scenes/cube_scene.h" #include "scenes/default_options_scene.h" #include "scenes/desktop_scene.h" #include "scenes/effect2d_scene.h" #include "scenes/shading_scene.h" #include "scenes/texture_scene.h" #include "scenes/vertex_scene.h" #include #include #include #include namespace { MainLoop* main_loop_global = nullptr; void sighandler(int) { main_loop_global->stop(); } void set_up_sighandler(MainLoop& main_loop) { main_loop_global = &main_loop; struct sigaction sa{}; sa.sa_handler = sighandler; sigaction(SIGTERM, &sa, nullptr); sigaction(SIGINT, &sa, nullptr); } void populate_scene_collection(SceneCollection& sc) { sc.register_scene(std::make_unique()); sc.register_scene(std::make_unique()); sc.register_scene(std::make_unique(sc)); sc.register_scene(std::make_unique()); sc.register_scene(std::make_unique()); sc.register_scene(std::make_unique()); sc.register_scene(std::make_unique()); sc.register_scene(std::make_unique()); } } int main(int argc, char **argv) try { Options options; if (!options.parse_args(argc, argv)) return 1; Log::init(argv[0], options.show_debug); WindowSystemLoader ws_loader{options}; ws_loader.load_window_system_options(); if (options.show_help) { std::cout << options.help_string(); return 0; } Util::set_data_dir(options.data_dir); SceneCollection sc; populate_scene_collection(sc); BenchmarkCollection bc{sc}; if (options.list_scenes) { sc.log_scene_info(); return 0; } auto& ws = ws_loader.load_window_system(); auto&& device_strategy = options.use_device_with_uuid.second ? VulkanState::ChoosePhysicalDeviceStrategy{ChooseByUUIDStrategy{options.use_device_with_uuid.first}} : VulkanState::ChoosePhysicalDeviceStrategy{ChooseFirstSupportedStrategy{}}; VulkanState vulkan{ws.vulkan_wsi(), device_strategy}; if (options.list_devices) { vulkan.log_all_devices(); return 0; } auto const ws_vulkan_deinit = Util::on_scope_exit([&] { ws.deinit_vulkan(); }); ws.init_vulkan(vulkan); Log::info("=======================================================\n"); Log::info(" vkmark %s\n", VKMARK_VERSION_STR); Log::info("=======================================================\n"); vulkan.log_info(); Log::info("=======================================================\n"); if (!options.benchmarks.empty()) bc.add(options.benchmarks); if (!bc.contains_normal_scenes()) bc.add(DefaultBenchmarks::get()); MainLoop main_loop{vulkan, ws, bc, options}; set_up_sighandler(main_loop); main_loop.run(); Log::info("=======================================================\n"); Log::info(" vkmark Score: %u\n", main_loop.score()); Log::info("=======================================================\n"); } catch (std::exception const& e) { Log::error("%s\n", e.what()); return 1; } vkmark-2017.08+git20220909/src/main_loop.cpp000066400000000000000000000074431441550741700200650ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "main_loop.h" #include "vulkan_state.h" #include "vulkan_image.h" #include "window_system.h" #include "benchmark_collection.h" #include "benchmark.h" #include "scene.h" #include "log.h" #include "options.h" #include "util.h" namespace { void log_scene_info(Scene& scene, bool show_all_options) { Log::info("%s", scene.info_string(show_all_options).c_str()); Log::flush(); } void log_scene_invalid(Scene& scene) { Log::warning("Skipping benchmark with invalid scene name '%s'\n", scene.name().c_str()); Log::flush(); } void log_scene_exception(std::string const& what) { auto const fmt = Log::continuation_prefix + " Failed with exception: %s\n"; Log::info(fmt.c_str(), what.c_str()); Log::flush(); } void log_scene_fps(unsigned int fps) { auto const fmt = Log::continuation_prefix + " FPS: %u FrameTime: %.3f ms\n"; Log::info(fmt.c_str(), fps, 1000.0 / fps); Log::flush(); } template void advance_iter(T& iter, T const& start, T const& end, bool run_forever) { ++iter; if (run_forever && iter == end) iter = start; } } MainLoop::MainLoop(VulkanState& vulkan, WindowSystem& ws, BenchmarkCollection& bc, Options const& options) : vulkan{vulkan}, ws{ws}, bc{bc}, options{options}, should_stop{false}, total_fps{0}, total_benchmarks{0} { } void MainLoop::run() { auto const& benchmarks = bc.benchmarks(); for (auto iter = benchmarks.begin(); iter != benchmarks.end(); advance_iter(iter, benchmarks.begin(), benchmarks.end(), options.run_forever)) try { auto& benchmark = *iter; auto& scene = benchmark->prepare_scene(); if (!scene.is_valid()) { log_scene_invalid(scene); continue; } // Scenes with empty names are option-setting scenes. // Just set them up and continue. if (scene.name().empty()) { scene.setup(vulkan, ws.vulkan_images()); continue; } log_scene_info(scene, options.show_all_options); auto const scene_teardown = Util::on_scope_exit([&] { scene.teardown(); }); scene.setup(vulkan, ws.vulkan_images()); bool should_quit = false; scene.start(); while (scene.is_running() && !(should_quit = ws.should_quit()) && !should_stop) { ws.present_vulkan_image( scene.draw(ws.next_vulkan_image())); scene.update(); } auto const scene_fps = scene.average_fps(); log_scene_fps(scene_fps); total_fps += scene_fps; ++total_benchmarks; if (should_quit || should_stop) break; } catch (std::exception const& e) { log_scene_exception(e.what()); } } void MainLoop::stop() { should_stop = true; } unsigned int MainLoop::score() { return total_benchmarks == 0 ? 0 : (total_fps / total_benchmarks); } vkmark-2017.08+git20220909/src/main_loop.h000066400000000000000000000025201441550741700175210ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include class VulkanState; class WindowSystem; class BenchmarkCollection; struct Options; class MainLoop { public: MainLoop( VulkanState& vulkan, WindowSystem& ws, BenchmarkCollection& bc, Options const& options); void run(); void stop(); unsigned int score(); private: VulkanState& vulkan; WindowSystem& ws; BenchmarkCollection& bc; Options const& options; std::atomic should_stop; unsigned int total_fps; unsigned int total_benchmarks; }; vkmark-2017.08+git20220909/src/managed_resource.h000066400000000000000000000040211441550741700210450ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include template struct ManagedResource { using Destructor = std::function; ManagedResource() = default; ManagedResource(T&& raw, Destructor&& destructor) : raw{raw}, destructor{std::move(destructor)} { } ManagedResource(ManagedResource&& rhs) : raw{std::move(rhs)}, destructor{std::move(rhs.destructor)} { rhs.raw = T{}; rhs.destructor = [](T&){}; } ~ManagedResource() { destructor(raw); } ManagedResource& operator=(ManagedResource&& rhs) { destructor(raw); raw = std::move(rhs.raw); destructor = std::move(rhs.destructor); rhs.raw = T{}; rhs.destructor = [](T&){}; return *this; } ManagedResource& operator=(ManagedResource& rhs) = delete; operator T const& () const { return raw; } operator T&() { return raw; } // For pointer types only T const operator->() const { return raw; } T operator->() { return raw; } T steal() { auto ret = raw; raw = T{}; destructor = [](T&){}; return ret; } T raw = T{}; std::function destructor = [](T&){}; }; vkmark-2017.08+git20220909/src/mesh.cpp000066400000000000000000000174601441550741700170440ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "mesh.h" #include #include namespace { std::vector vk_formats_to_float_formats( std::vector const& formats) { std::vector ret; for (auto const f : formats) { switch (f) { case vk::Format::eR32Sfloat: ret.push_back(1); break; case vk::Format::eR32G32Sfloat: ret.push_back(2); break; case vk::Format::eR32G32B32Sfloat: ret.push_back(3); break; case vk::Format::eR32G32B32A32Sfloat: ret.push_back(4); break; default: throw std::runtime_error{"Unsupported vertex format " + to_string(f)}; }; } return ret; } size_t calc_vertex_num_floats(std::vector const& formats) { return std::accumulate(formats.begin(), formats.end(), 0); } } Mesh::Mesh(std::vector const& vk_formats) : vk_formats{vk_formats}, formats{vk_formats_to_float_formats(vk_formats)}, vertex_num_floats{calc_vertex_num_floats(formats)}, interleave{false} { } void Mesh::set_interleave(bool interleave_) { interleave = interleave_; } void Mesh::next_vertex() { vertices.push_back(std::vector(vertex_num_floats)); } size_t Mesh::num_vertices() const { return vertices.size(); } void Mesh::set_attribute(size_t pos, float data) { if (formats[pos] != 1) throw std::logic_error{"Trying to set vertex attribute with incorrectly sized data"}; auto const offset = std::accumulate(formats.begin(), formats.begin() + pos, 0); auto& vertex = vertices.back(); vertex[offset] = data; } void Mesh::set_attribute(size_t pos, glm::vec2 const& data) { if (formats[pos] != 2) throw std::logic_error{"Trying to set vertex attribute with incorrectly sized data"}; auto const offset = std::accumulate(formats.begin(), formats.begin() + pos, 0); auto& vertex = vertices.back(); vertex[offset] = data.x; vertex[offset + 1] = data.y; } void Mesh::set_attribute(size_t pos, glm::vec3 const& data) { if (formats[pos] != 3) throw std::logic_error{"Trying to set vertex attribute with incorrectly sized data"}; auto const offset = std::accumulate(formats.begin(), formats.begin() + pos, 0); auto& vertex = vertices.back(); vertex[offset] = data.x; vertex[offset + 1] = data.y; vertex[offset + 2] = data.z; } void Mesh::set_attribute(size_t pos, glm::vec4 const& data) { if (formats[pos] != 4) throw std::logic_error{"Trying to set vertex attribute with incorrectly sized data"}; auto const offset = std::accumulate(formats.begin(), formats.begin() + pos, 0); auto& vertex = vertices.back(); vertex[offset] = data.x; vertex[offset + 1] = data.y; vertex[offset + 2] = data.z; vertex[offset + 3] = data.w; } glm::vec3 Mesh::min_attribute_bound(size_t pos) { if (formats[pos] != 3) throw std::logic_error{"Trying to get min attribute bound from incorrectly sized data"}; auto const offset = std::accumulate(formats.begin(), formats.begin() + pos, 0); glm::vec3 ret{std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()}; for (auto const& v : vertices) { if (v[offset] < ret.x) ret.x = v[offset]; if (v[offset + 1] < ret.y) ret.y = v[offset + 1]; if (v[offset + 2] < ret.z) ret.z = v[offset + 2]; } return ret; } glm::vec3 Mesh::max_attribute_bound(size_t pos) { if (formats[pos] != 3) throw std::logic_error{"Trying to get max attribute bound from incorrectly sized data"}; auto const offset = std::accumulate(formats.begin(), formats.begin() + pos, 0); glm::vec3 ret{std::numeric_limits::min(), std::numeric_limits::min(), std::numeric_limits::min()}; for (auto const& v : vertices) { if (v[offset] > ret.x) ret.x = v[offset]; if (v[offset + 1] > ret.y) ret.y = v[offset + 1]; if (v[offset + 2] > ret.z) ret.z = v[offset + 2]; } return ret; } std::vector Mesh::binding_descriptions() const { std::vector ret; if (interleave) { ret.push_back( vk::VertexInputBindingDescription{} .setBinding(0) .setStride(sizeof(float) * vertex_num_floats) .setInputRate(vk::VertexInputRate::eVertex)); } else { int binding = 0; for (auto const& f : formats) { ret.push_back( vk::VertexInputBindingDescription{} .setBinding(binding) .setStride(sizeof(float) * f) .setInputRate(vk::VertexInputRate::eVertex)); ++binding; } } return ret; } std::vector Mesh::attribute_descriptions() const { std::vector ret; int binding = 0; int i = 0; for (auto const& vf : vk_formats) { auto const offset = interleave ? sizeof(float) * std::accumulate(formats.begin(), formats.begin() + i, 0) : 0; ret.push_back( vk::VertexInputAttributeDescription{} .setBinding(binding) .setLocation(i) .setFormat(vf) .setOffset(offset)); if (!interleave) ++binding; ++i; } return ret; } void Mesh::copy_vertex_data_to(void* dst) const { auto const dst_c = static_cast(dst); if (interleave) { auto current = dst_c; for (auto const& vertex : vertices) { auto const nbytes = vertex.size() * sizeof(float); memcpy(current, vertex.data(), nbytes); current += nbytes; } } else { auto current = dst_c; for (size_t i = 0; i < formats.size(); ++i) { auto const offset = std::accumulate(formats.begin(), formats.begin() + i, 0); auto const nbytes = formats[i] * sizeof(float); for (auto const& vertex : vertices) { memcpy(current, &vertex[offset], nbytes); current += nbytes; } } } } std::vector Mesh::vertex_data_binding_offsets() const { std::vector ret; if (interleave) { ret.push_back(0); } else { for (size_t i = 0; i < formats.size(); ++i) { auto attrib_offset = std::accumulate(formats.begin(), formats.begin() + i, 0); ret.push_back(attrib_offset * sizeof(float) * vertices.size()); } } return ret; } size_t Mesh::vertex_data_size() const { return vertices.size() * vertex_num_floats * sizeof(float); } vkmark-2017.08+git20220909/src/mesh.h000066400000000000000000000036131441550741700165040ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include class VertexData; class Mesh { public: Mesh(std::vector const& formats); void set_interleave(bool interleave_); void next_vertex(); size_t num_vertices() const; void set_attribute(size_t pos, float data); void set_attribute(size_t pos, glm::vec2 const& data); void set_attribute(size_t pos, glm::vec3 const& data); void set_attribute(size_t pos, glm::vec4 const& data); glm::vec3 min_attribute_bound(size_t pos); glm::vec3 max_attribute_bound(size_t pos); // Vulkan related std::vector binding_descriptions() const; std::vector attribute_descriptions() const; size_t vertex_data_size() const; void copy_vertex_data_to(void* dst) const; std::vector vertex_data_binding_offsets() const; private: std::vector const vk_formats; std::vector const formats; size_t const vertex_num_floats; bool interleave; std::vector> vertices; }; vkmark-2017.08+git20220909/src/meson.build000066400000000000000000000073461441550741700175500ustar00rootroot00000000000000prog_python = find_program('python3') vulkan_hpp = join_paths([ vulkan_dep.get_pkgconfig_variable('includedir'), 'vulkan', 'vulkan.hpp' ]) format_map_gen_h = custom_target( 'format_map_gen.h', output: 'format_map_gen.h', input: vulkan_hpp, command: [prog_python, files('gen_format_map.py'), '@INPUT@', '@OUTPUT@'] ) core_sources = files( 'benchmark.cpp', 'benchmark_collection.cpp', 'default_benchmarks.cpp', 'device_uuid.cpp', 'log.cpp', 'main_loop.cpp', 'mesh.cpp', 'model.cpp', 'options.cpp', 'scene.cpp', 'scene_collection.cpp', 'util.cpp', 'vulkan_state.cpp', 'window_system_loader.cpp' ) + [format_map_gen_h] vkutil_sources = files( 'vkutil/buffer_builder.cpp', 'vkutil/copy_buffer.cpp', 'vkutil/descriptor_set_builder.cpp', 'vkutil/find_matching_memory_type.cpp', 'vkutil/framebuffer_builder.cpp', 'vkutil/image_builder.cpp', 'vkutil/image_view_builder.cpp', 'vkutil/map_memory.cpp', 'vkutil/one_time_command_buffer.cpp', 'vkutil/pipeline_builder.cpp', 'vkutil/render_pass_builder.cpp', 'vkutil/semaphore_builder.cpp', 'vkutil/texture_builder.cpp', 'vkutil/transition_image_layout.cpp' ) scene_sources = files( 'scenes/clear_scene.cpp', 'scenes/cube_scene.cpp', 'scenes/default_options_scene.cpp', 'scenes/desktop_scene.cpp', 'scenes/effect2d_scene.cpp', 'scenes/shading_scene.cpp', 'scenes/texture_scene.cpp', 'scenes/vertex_scene.cpp', ) vkmark_core = static_library( 'vkmark-core', core_sources, dependencies : [vulkan_dep, dl_dep, assimp_dep] ) vkmark = executable( 'vkmark', files('main.cpp') + vkutil_sources + scene_sources, link_with: vkmark_core, dependencies : [vulkan_dep, glm_dep, dl_dep], link_args: ['-Wl,--dynamic-list=' + join_paths([meson.current_source_dir(), 'dynamic.list'])], install : true ) if build_xcb_ws xcb_ws = shared_module( 'xcb', 'ws/xcb_window_system_plugin.cpp', 'ws/xcb_native_system.cpp', 'ws/swapchain_window_system.cpp', dependencies : [vulkan_dep, xcb_dep, xcb_icccm_dep], name_prefix : '', install : true, install_dir : ws_dir ) endif if build_wayland_ws wayland_scanner = find_program(wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner')) wayland_protocols_dir = wayland_protocols_dep.get_pkgconfig_variable('pkgdatadir') xdg_shell_xml_path = wayland_protocols_dir + '/stable/xdg-shell/xdg-shell.xml' xdg_shell_client_header = custom_target( 'xdg-shell client-header', command: [ wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@' ], input: xdg_shell_xml_path, output: 'xdg-shell-client-protocol.h', ) xdg_shell_private_code = custom_target( 'xdg-shell private-code', command: [ wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@' ], input: xdg_shell_xml_path, output: 'xdg-shell-protocol.c', ) wayland_ws = shared_module( 'wayland', 'ws/wayland_window_system_plugin.cpp', 'ws/wayland_native_system.cpp', 'ws/swapchain_window_system.cpp', xdg_shell_client_header, xdg_shell_private_code, dependencies : [vulkan_dep, wayland_client_dep], name_prefix : '', install : true, install_dir : ws_dir ) endif if build_kms_ws kms_ws = shared_module( 'kms', 'ws/kms_window_system_plugin.cpp', 'ws/kms_window_system.cpp', 'ws/atomic_kms_window_system.cpp', dependencies : [vulkan_dep, libdrm_dep, gbm_dep], name_prefix : '', install : true, install_dir : ws_dir ) endif vkmark-2017.08+git20220909/src/model.cpp000066400000000000000000000112511441550741700172000ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "model.h" #include "util.h" #include "mesh.h" #include #include #include namespace { unsigned int const post_process_flags = aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_GenNormals | aiProcess_JoinIdenticalVertices; } ModelAttribMap::ModelAttribMap() : position{-1}, color{-1}, normal{-1}, texcoord{-1} { } ModelAttribMap& ModelAttribMap::with_position(vk::Format format) { position = formats.size(); formats.push_back(format); return *this; } ModelAttribMap& ModelAttribMap::with_color(vk::Format format) { color = formats.size(); formats.push_back(format); return *this; } ModelAttribMap& ModelAttribMap::with_normal(vk::Format format) { normal = formats.size(); formats.push_back(format); return *this; } ModelAttribMap& ModelAttribMap::with_texcoord(vk::Format format) { texcoord = formats.size(); formats.push_back(format); return *this; } ModelAttribMap& ModelAttribMap::with_other(vk::Format format) { formats.push_back(format); return *this; } Model::Model(std::string const& model_file) { importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT); auto const path = Util::get_data_file_path("models/" + model_file); if (!importer.ReadFile(path.c_str(), post_process_flags)) throw std::runtime_error{"Failed to parse model file " + model_file}; } Model::Model(std::string const& model_str, std::string const& model_type) { importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT); if (!importer.ReadFileFromMemory(model_str.data(), model_str.size(), post_process_flags, model_type.c_str())) { throw std::runtime_error{"Failed to parse model string of type " + model_type}; } } Model::~Model() = default; std::unique_ptr Model::to_mesh(ModelAttribMap const& map) { auto mesh = std::make_unique(map.formats); auto const scene = importer.GetScene(); for (auto m = 0u; m < scene->mNumMeshes; ++m) { auto const aimesh = scene->mMeshes[m]; auto const colors = aimesh->mColors[0]; auto const texcoords = aimesh->mTextureCoords[0]; for (auto f = 0u; f < aimesh->mNumFaces; ++f) { auto const& face = aimesh->mFaces[f]; for (auto i = 0u; i < face.mNumIndices; ++i) { auto const vindex = face.mIndices[i]; auto const& vertex = aimesh->mVertices[vindex]; auto const& normal = aimesh->mNormals[vindex]; mesh->next_vertex(); if (map.position >= 0) mesh->set_attribute(map.position, {vertex.x, -vertex.y, vertex.z}); if (map.normal >= 0) mesh->set_attribute(map.normal, {normal.x, -normal.y, normal.z}); if (map.color >= 0) { if (colors) { auto const& color = colors[vindex]; mesh->set_attribute(map.color, {color.r, color.g, color.b}); } else { mesh->set_attribute(map.color, {1, 1, 1}); } } if (map.texcoord >= 0) { if (texcoords) { auto const& texcoord = texcoords[vindex]; mesh->set_attribute(map.texcoord, {texcoord.x, 1.0 - texcoord.y}); } else { mesh->set_attribute(map.texcoord, {0, 0}); } } } } } return mesh; } vkmark-2017.08+git20220909/src/model.h000066400000000000000000000031141441550741700166440ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include #include class Mesh; class ModelAttribMap { public: ModelAttribMap(); ModelAttribMap& with_position(vk::Format format); ModelAttribMap& with_color(vk::Format format); ModelAttribMap& with_normal(vk::Format format); ModelAttribMap& with_texcoord(vk::Format format); ModelAttribMap& with_other(vk::Format format); std::vector formats; ssize_t position; ssize_t color; ssize_t normal; ssize_t texcoord; }; class Model { public: Model(std::string const& model_file); Model(std::string const& model_str, std::string const& model_type); ~Model(); std::unique_ptr to_mesh(ModelAttribMap const& map); private: Assimp::Importer importer; }; vkmark-2017.08+git20220909/src/options.cpp000066400000000000000000000174471441550741700176100ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include #include #include #include #include #include #include "options.h" #include "util.h" #include "format_map_gen.h" // Generated map from string to vk::Format namespace { struct option long_options[] = { {"benchmark", 1, 0, 0}, {"size", 1, 0, 0}, {"use-device", 1, 0, 0}, {"fullscreen", 0, 0, 0}, {"present-mode", 1, 0, 0}, {"pixel-format", 1, 0, 0}, {"list-scenes", 0, 0, 0}, {"show-all-options", 0, 0, 0}, {"winsys-dir", 1, 0, 0}, {"data-dir", 1, 0, 0}, {"winsys", 1, 0, 0}, {"winsys-options", 1, 0, 0}, {"list-devices", 0, 0, 0}, {"run-forever", 0, 0, 0}, {"debug", 0, 0, 0}, {"help", 0, 0, 0}, {0, 0, 0, 0} }; std::pair parse_size(std::string const& str) { std::pair size; auto const dimensions = Util::split(str, 'x'); size.first = Util::from_string(dimensions[0]); /* * Parse the second element (height). If there is none, use the value * of the first element for the second (width = height) */ if (dimensions.size() > 1) size.second = Util::from_string(dimensions[1]); else size.second = size.first; return size; } vk::PresentModeKHR parse_present_mode(std::string const& str) { if (str == "immediate") return vk::PresentModeKHR::eImmediate; else if (str == "mailbox") return vk::PresentModeKHR::eMailbox; else if (str == "fifo") return vk::PresentModeKHR::eFifo; else if (str == "fiforelaxed") return vk::PresentModeKHR::eFifoRelaxed; else return vk::PresentModeKHR::eMailbox; } std::string normalize_pixel_format(std::string str) { str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); std::transform(str.begin(), str.end(), str.begin(), [](auto c) { return std::toupper(c); }); return str; } vk::Format parse_pixel_format(std::string const& str) { auto const format_iter = format_map.find(normalize_pixel_format(str)); if (format_iter != format_map.end()) return format_iter->second; return vk::Format::eUndefined; } std::vector parse_window_system_options( std::string const& options_str) { std::vector ret; auto const opts = Util::split(options_str, ':'); for (auto const& opt : opts) { auto const kv = Util::split(opt, '='); if (kv.size() == 2) ret.push_back({kv[0], kv[1]}); else throw std::runtime_error{"Invalid window system option '" + opt + "'"}; } return ret; } } Options::Options() : size{800, 600}, present_mode{vk::PresentModeKHR::eMailbox}, pixel_format{vk::Format::eUndefined}, list_scenes{false}, show_all_options{false}, window_system_dir{VKMARK_WINDOW_SYSTEM_DIR}, data_dir{VKMARK_DATA_DIR}, run_forever{false}, show_debug{false}, show_help{false}, list_devices{false}, use_device_with_uuid{} { } std::string Options::help_string() { std::string help = "A benchmark for Vulkan\n" "\n" "Options:\n" " -b, --benchmark BENCH A benchmark to run: 'scene(:opt1=val1)*'\n" " (the option can be used multiple times)\n" " -s, --size WxH Size of the output window (default: 800x600)\n" " --fullscreen Run fullscreen (equivalent to --size -1x-1)\n" " -p, --present-mode PM Vulkan present mode (default: mailbox)\n" " [immediate, mailbox, fifo, fiforelaxed]\n" " --pixel-format PF Vulkan pixel format (default: choose best)\n" " -l, --list-scenes Display information about the available scenes\n" " and their options\n" " --show-all-options Show all scene option values used for benchmarks\n" " (only explicitly set options are shown by default)\n" " --winsys-dir DIR Directory to search in for window system plugins\n" " --data-dir DIR Directory to search in for scene data files\n" " --winsys WS Window system plugin to use (default: choose best)\n" " [xcb, wayland, kms]\n" " --winsys-options OPTS Window system options as 'opt1=val1(:opt2=val2)*'\n" " --run-forever Run indefinitely, looping from the last benchmark\n" " back to the first\n" " -d, --debug Display debug messages\n" " -D --use-device Use Vulkan device with specified UUID\n" " -L --list-devices List Vulkan devices\n" " -h, --help Display help\n"; for (auto const& wsh : window_system_help) help += wsh; return help; } bool Options::parse_args(int argc, char **argv) { // Reset optind to allow multiple invocations of parse_args optind = 0; while (true) { int option_index = -1; int c; std::string optname; c = getopt_long(argc, argv, "b:s:p:ldhD:L", long_options, &option_index); if (c == -1) break; if (c == ':' || c == '?') return false; if (option_index != -1) optname = long_options[option_index].name; if (c == 'b' || optname == "benchmark") benchmarks.push_back(optarg); else if (c == 's' || optname == "size") size = parse_size(optarg); else if (optname == "fullscreen") size = {-1, -1}; else if (c == 'p' || optname == "present-mode") present_mode = parse_present_mode(optarg); else if (optname == "pixel-format") pixel_format = parse_pixel_format(optarg); else if (c == 'l' || optname == "list-scenes") list_scenes = true; else if (optname == "show-all-options") show_all_options = true; else if (optname == "winsys-dir") window_system_dir = optarg; else if (optname == "data-dir") data_dir = optarg; else if (optname == "winsys") window_system = optarg; else if (optname == "winsys-options") window_system_options = parse_window_system_options(optarg); else if (optname == "run-forever") run_forever = true; else if (c == 'd' || optname == "debug") show_debug = true; else if (c == 'h' || optname == "help") show_help = true; else if (c == 'L' || optname == "list-devices") list_devices = true; else if (c == 'D' || optname == "use-device") use_device_with_uuid = std::make_pair(DeviceUUID{std::string {optarg}}, true); } return true; } void Options::add_window_system_help(std::string const& help) { window_system_help.push_back(help); } vkmark-2017.08+git20220909/src/options.h000066400000000000000000000033321441550741700172410ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include #include #include #include "device_uuid.h" struct Options { struct WindowSystemOption { std::string name; std::string value; }; Options(); bool parse_args(int argc, char **argv); std::string help_string(); void add_window_system_help(std::string const& help); std::vector benchmarks; std::pair size; vk::PresentModeKHR present_mode; vk::Format pixel_format; bool list_scenes; bool show_all_options; std::string window_system_dir; std::string data_dir; std::string window_system; std::vector window_system_options; bool run_forever; bool show_debug; bool show_help; bool list_devices; std::pair use_device_with_uuid; // pseudo-optional private: std::vector window_system_help; }; vkmark-2017.08+git20220909/src/scene.cpp000066400000000000000000000100001441550741700171640ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "scene.h" #include "vulkan_image.h" #include "util.h" #include "options.h" SceneOption::SceneOption(std::string const& name, std::string const& value, std::string const& description, std::string const& values) : name{name}, value{value}, default_value{value}, description{description}, acceptable_values{Util::split(values, ',')} { } bool SceneOption::accepts_value(std::string const& val) { return acceptable_values.empty() || std::find(acceptable_values.begin(), acceptable_values.end(), val) != acceptable_values.end(); } Scene::Scene(std::string const& name) : name_{name}, start_time{0}, last_update_time{0}, current_frame{0}, running{false}, duration{0} { options_["duration"] = SceneOption("duration", "10.0", "The duration of each benchmark in seconds"); } bool Scene::is_valid() const { return true; } void Scene::setup(VulkanState&, std::vector const&) { duration = 1000000.0 * Util::from_string(options_["duration"].value); } void Scene::teardown() { } void Scene::start() { current_frame = 0; running = true; start_time = Util::get_timestamp_us(); last_update_time = start_time; } VulkanImage Scene::draw(VulkanImage const& image) { return image.copy_with_semaphore({}); } void Scene::update() { auto const current_time = Util::get_timestamp_us(); auto const elapsed_time = current_time - start_time; ++current_frame; last_update_time = current_time; if (elapsed_time >= duration) running = false; } std::string Scene::name() const { return name_; } std::string Scene::info_string(bool show_all_options) const { std::stringstream ss; ss << "[" << name_ << "] "; bool option_shown = false; for (auto const& kv : options_) { if (show_all_options || kv.second.set) { ss << kv.first << "=" << kv.second.value << ":"; option_shown = true; } } if (!option_shown) ss << ":"; return ss.str(); } unsigned int Scene::average_fps() const { double const elapsed_time_sec = (last_update_time - start_time) / 1000000.0; return current_frame / elapsed_time_sec; } bool Scene::is_running() const { return running; } bool Scene::set_option(std::string const& opt, std::string const& val) { auto const iter = options_.find(opt); if (iter != options_.end() && iter->second.accepts_value(val)) { iter->second.value = val; iter->second.set = true; return true; } return false; } void Scene::reset_options() { for (auto& kv : options_) { auto& opt = kv.second; opt.value = opt.default_value; opt.set = false; } } bool Scene::set_option_default(std::string const& opt, std::string const& val) { auto const iter = options_.find(opt); if (iter != options_.end() && iter->second.accepts_value(val)) { iter->second.default_value = val; return true; } return false; } std::unordered_map const& Scene::options() const { return options_; } vkmark-2017.08+git20220909/src/scene.h000066400000000000000000000044611441550741700166470ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include #include #include class SceneOption { public: SceneOption(std::string const& name, std::string const& value, std::string const& desc, std::string const& values = ""); SceneOption() = default; bool accepts_value(std::string const& val); std::string name; std::string value; std::string default_value; std::string description; std::vector acceptable_values; bool set; }; class VulkanState; struct VulkanImage; class Scene { public: virtual ~Scene() = default; virtual bool is_valid() const; virtual void setup(VulkanState&, std::vector const&); virtual void teardown(); virtual void start(); virtual VulkanImage draw(VulkanImage const&); virtual void update(); std::string name() const; std::string info_string(bool show_all_options) const; unsigned int average_fps() const; bool is_running() const; bool set_option(std::string const& opt, std::string const& val); void reset_options(); bool set_option_default(std::string const& opt, std::string const& val); std::unordered_map const& options() const; protected: Scene(std::string const& name); std::string const name_; std::unordered_map options_; uint64_t start_time; uint64_t last_update_time; uint64_t current_frame; bool running; uint64_t duration; }; vkmark-2017.08+git20220909/src/scene_collection.cpp000066400000000000000000000067171441550741700214230ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "scene_collection.h" #include "scene.h" #include "log.h" namespace { class InvalidScene : public Scene { public: InvalidScene(std::string const& name) : Scene{name} {} bool is_valid() const override { return false; } }; } SceneCollection::SceneCollection() { } void SceneCollection::register_scene(std::unique_ptr scene) { scene_map[scene->name()] = std::move(scene); } Scene& SceneCollection::get_scene_by_name(std::string const& name) { auto const iter = scene_map.find(name); if (iter != scene_map.end()) { return *(iter->second); } else { scene_map[name] = std::make_unique(name); return *scene_map[name]; } } void SceneCollection::set_option_default(std::string const& name, std::string const& value) { for (auto const& kv : scene_map) { Scene& scene = *kv.second; /* * Display warning only if the option value is unsupported, not if * the scene doesn't support the option at all. */ if (!scene.set_option_default(name, value) && scene.options().find(name) != scene.options().end()) { Log::warning("Scene '%s' doesn't accept default value '%s' for option '%s'\n", scene.name().c_str(), value.c_str(), name.c_str()); } } } void SceneCollection::log_scene_info() { for (auto const& kv : scene_map) { auto const scene = kv.second.get(); if (scene->name().empty()) continue; Log::info("[Scene] %s\n", scene->name().c_str()); for (auto const& opt_kv : scene->options()) { auto const& opt = opt_kv.second; Log::info(" [Option] %s\n" " Description : %s\n" " Default Value: %s\n", opt.name.c_str(), opt.description.c_str(), opt.default_value.c_str()); /* Display list of acceptable values (if defined) */ if (!opt.acceptable_values.empty()) { Log::info(" Acceptable Values: "); for (auto val_iter = opt.acceptable_values.cbegin(); val_iter != opt.acceptable_values.cend(); ++val_iter) { std::string format_value{Log::continuation_prefix + "%s"}; if (val_iter + 1 != opt.acceptable_values.end()) format_value += ","; else format_value += "\n"; Log::info(format_value.c_str(), val_iter->c_str()); } } } } } vkmark-2017.08+git20220909/src/scene_collection.h000066400000000000000000000023471441550741700210630ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include #include class Scene; class SceneCollection { public: SceneCollection(); void register_scene(std::unique_ptr scene); Scene& get_scene_by_name(std::string const& name); void set_option_default(std::string const& name, std::string const& value); void log_scene_info(); private: std::unordered_map> scene_map; }; vkmark-2017.08+git20220909/src/scenes/000077500000000000000000000000001441550741700166545ustar00rootroot00000000000000vkmark-2017.08+git20220909/src/scenes/clear_scene.cpp000066400000000000000000000152341441550741700216300ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "clear_scene.h" #include "util.h" #include "vulkan_state.h" #include "vulkan_image.h" #include "vkutil/vkutil.h" #include ClearScene::ClearScene() : Scene{"clear"}, cycle{true} { options_["color"] = SceneOption("color", "cycle", "The normalized (0.0-1.0) \"r,g,b,a\" color to use or \"cycle\" to cycle"); } void ClearScene::setup(VulkanState& vulkan_, std::vector const& images) { Scene::setup(vulkan_, images); vulkan = &vulkan_; auto const command_buffer_allocate_info = vk::CommandBufferAllocateInfo{} .setCommandPool(vulkan->command_pool()) .setCommandBufferCount(images.size()) .setLevel(vk::CommandBufferLevel::ePrimary); command_buffers = vulkan->device().allocateCommandBuffers(command_buffer_allocate_info); command_buffer_fences.resize(command_buffers.size()); submit_semaphore = vkutil::SemaphoreBuilder{*vulkan}.build(); if (options_["color"].value == "cycle") { cycle = true; std::array const color_value{{1.0f,0.0f,0.0f,1.0f}}; clear_color = vk::ClearColorValue{color_value}; } else { cycle = false; auto const components = Util::split(options_["color"].value, ','); std::array color_value{{0.0f,0.0f,0.0f,1.0f}}; if (components.size() > color_value.size()) throw std::runtime_error("too many components in \"color\" option"); for (size_t i = 0; i < components.size(); ++i) color_value[i] = std::stof(components[i]); clear_color = vk::ClearColorValue{color_value}; } } void ClearScene::teardown() { vulkan->device().waitIdle(); submit_semaphore = {}; for (auto const& fence : command_buffer_fences) { if (fence) vulkan->device().destroyFence(fence); } command_buffer_fences.clear(); vulkan->device().freeCommandBuffers(vulkan->command_pool(), command_buffers); Scene::teardown(); } void ClearScene::prepare_command_buffer(VulkanImage const& image) { auto const begin_info = vk::CommandBufferBeginInfo{} .setFlags(vk::CommandBufferUsageFlagBits::eSimultaneousUse); auto const image_range = vk::ImageSubresourceRange{} .setAspectMask(vk::ImageAspectFlagBits::eColor) .setBaseMipLevel(0) .setLevelCount(1) .setBaseArrayLayer(0) .setLayerCount(1); auto const undef_to_transfer_barrier = vk::ImageMemoryBarrier{} .setImage(image.image) .setOldLayout(vk::ImageLayout::eUndefined) .setNewLayout(vk::ImageLayout::eTransferDstOptimal) .setSrcAccessMask({}) .setDstAccessMask(vk::AccessFlagBits::eTransferWrite) .setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) .setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) .setSubresourceRange(image_range); auto const transfer_to_present_barrier = vk::ImageMemoryBarrier{} .setImage(image.image) .setOldLayout(vk::ImageLayout::eTransferDstOptimal) .setNewLayout(vk::ImageLayout::ePresentSrcKHR) .setSrcAccessMask(vk::AccessFlagBits::eTransferWrite) .setDstAccessMask({}) .setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) .setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) .setSubresourceRange(image_range); auto const i = image.index; if (!command_buffer_fences[i]) { command_buffer_fences[i] = vulkan->device().createFence(vk::FenceCreateInfo()); } else { vulkan->device().waitForFences(command_buffer_fences[i], true, INT64_MAX); vulkan->device().resetFences(command_buffer_fences[i]); } command_buffers[i].begin(begin_info); command_buffers[i].pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, undef_to_transfer_barrier); command_buffers[i].clearColorImage( image.image, vk::ImageLayout::eTransferDstOptimal, clear_color, image_range); command_buffers[i].pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eBottomOfPipe, {}, {}, {}, transfer_to_present_barrier); command_buffers[i].end(); } VulkanImage ClearScene::draw(VulkanImage const& image) { prepare_command_buffer(image); vk::PipelineStageFlags mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; auto const submit_info = vk::SubmitInfo{} .setSignalSemaphoreCount(1) .setPSignalSemaphores(&submit_semaphore.raw) .setCommandBufferCount(1) .setPCommandBuffers(&command_buffers[image.index]) .setWaitSemaphoreCount(image.semaphore ? 1 : 0) .setPWaitSemaphores(&image.semaphore) .setPWaitDstStageMask(&mask); vulkan->graphics_queue().submit(submit_info, command_buffer_fences[image.index]); return image.copy_with_semaphore(submit_semaphore); } void ClearScene::update() { auto const elapsed = Util::get_timestamp_us() - start_time; if (cycle) { // HSV to RGB conversion for S=V=1 and H completeling a cycle every 5sec double const period = 5000000.0; float const c = 1.0; float const h = (360 / 60) * std::fmod(elapsed, period) / period; float const x = c * (1 - std::fabs(std::fmod(h, 2.0) - 1)); float r = 0.0f; float g = 0.0f; float b = 0.0f; switch (static_cast(h)) { case 0: r = c; g = x; b = 0; break; case 1: r = x; g = c; b = 0; break; case 2: r = 0; g = c; b = x; break; case 3: r = 0; g = x; b = c; break; case 4: r = x; g = 0; b = c; break; case 5: r = c; g = 0; b = x; break; default: r = g = b = 0; break; }; clear_color = vk::ClearColorValue{std::array{{r, g, b, 1.0f}}}; } Scene::update(); } vkmark-2017.08+git20220909/src/scenes/clear_scene.h000066400000000000000000000026521441550741700212750ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "scene.h" #include "managed_resource.h" #include class ClearScene : public Scene { public: ClearScene(); void setup(VulkanState&, std::vector const&) override; void teardown() override; VulkanImage draw(VulkanImage const&) override; void update() override; private: void prepare_command_buffer(VulkanImage const& image); VulkanState* vulkan; std::vector command_buffers; std::vector command_buffer_fences; ManagedResource submit_semaphore; vk::ClearColorValue clear_color; bool cycle; }; vkmark-2017.08+git20220909/src/scenes/cube_scene.cpp000066400000000000000000000215651441550741700214640ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ // Vertex data based on kmscube/vkcube examples #include "cube_scene.h" #include "mesh.h" #include "model.h" #include "util.h" #include "vulkan_state.h" #include "vulkan_image.h" #include "vkutil/vkutil.h" #include #include #include namespace { struct Uniforms { glm::mat4 modelview; glm::mat4 modelviewprojection; glm::mat4 normal; }; } CubeScene::CubeScene() : Scene{"cube"} { } CubeScene::~CubeScene() = default; void CubeScene::setup( VulkanState& vulkan_, std::vector const& vulkan_images) { Scene::setup(vulkan_, vulkan_images); vulkan = &vulkan_; extent = vulkan_images[0].extent; format = vulkan_images[0].format; aspect = static_cast(extent.height) / extent.width; mesh = Model{"kmscube.ply"}.to_mesh( ModelAttribMap{} .with_position(vk::Format::eR32G32B32Sfloat) .with_color(vk::Format::eR32G32B32Sfloat) .with_normal(vk::Format::eR32G32B32Sfloat)); setup_vertex_buffer(); setup_uniform_buffer(); setup_uniform_descriptor_set(); setup_render_pass(); setup_pipeline(); setup_framebuffers(vulkan_images); setup_command_buffers(); submit_semaphore = vkutil::SemaphoreBuilder{*vulkan}.build(); rotation = {45.0f, 45.0f, 10.0f}; } void CubeScene::teardown() { vulkan->device().waitIdle(); submit_semaphore = {}; vulkan->device().freeCommandBuffers(vulkan->command_pool(), command_buffers); framebuffers.clear(); image_views.clear(); pipeline = {}; pipeline_layout = {}; render_pass = {}; descriptor_set = {}; uniform_buffer_map = {}; uniform_buffer = {}; vertex_buffer = {}; Scene::teardown(); } VulkanImage CubeScene::draw(VulkanImage const& image) { update_uniforms(); vk::PipelineStageFlags const mask = vk::PipelineStageFlagBits::eTopOfPipe; auto const submit_info = vk::SubmitInfo{} .setCommandBufferCount(1) .setPCommandBuffers(&command_buffers[image.index]) .setWaitSemaphoreCount(image.semaphore ? 1 : 0) .setPWaitSemaphores(&image.semaphore) .setPWaitDstStageMask(&mask) .setSignalSemaphoreCount(1) .setPSignalSemaphores(&submit_semaphore.raw); vulkan->graphics_queue().submit(submit_info, {}); return image.copy_with_semaphore(submit_semaphore); } void CubeScene::update() { auto const t = (Util::get_timestamp_us() - start_time) / 5000.0; rotation = {45.0f + (0.25f * t), 45.0f + (0.5f * t), 10.0f + (0.15f * t)}; Scene::update(); } void CubeScene::setup_vertex_buffer() { vk::DeviceMemory vertex_buffer_memory; vertex_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage(vk::BufferUsageFlagBits::eVertexBuffer) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(vertex_buffer_memory) .build(); auto const vertex_buffer_map = vkutil::map_memory( *vulkan, vertex_buffer_memory, 0, mesh->vertex_data_size()); mesh->copy_vertex_data_to(vertex_buffer_map); } void CubeScene::setup_uniform_buffer() { uniform_buffer = vkutil::BufferBuilder{*vulkan} .set_size(sizeof(Uniforms)) .set_usage(vk::BufferUsageFlagBits::eUniformBuffer) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(uniform_buffer_memory) .build(); uniform_buffer_map = vkutil::map_memory( *vulkan, uniform_buffer_memory, 0, sizeof(Uniforms)); } void CubeScene::setup_uniform_descriptor_set() { descriptor_set = vkutil::DescriptorSetBuilder{*vulkan} .set_type(vk::DescriptorType::eUniformBuffer) .set_stage_flags(vk::ShaderStageFlagBits::eVertex) .set_buffer(uniform_buffer, 0, sizeof(Uniforms)) .set_layout_out(descriptor_set_layout) .build(); } void CubeScene::setup_render_pass() { render_pass = vkutil::RenderPassBuilder(*vulkan) .set_color_format(format) .set_color_load_op(vk::AttachmentLoadOp::eClear) .build(); } void CubeScene::setup_pipeline() { auto const pipeline_layout_create_info = vk::PipelineLayoutCreateInfo{} .setSetLayoutCount(1) .setPSetLayouts(&descriptor_set_layout); pipeline_layout = ManagedResource{ vulkan->device().createPipelineLayout(pipeline_layout_create_info), [this] (auto const& pl) { vulkan->device().destroyPipelineLayout(pl); }}; pipeline = vkutil::PipelineBuilder(*vulkan) .set_extent(extent) .set_layout(pipeline_layout) .set_render_pass(render_pass) .set_vertex_shader(Util::read_data_file("shaders/vkcube.vert.spv")) .set_fragment_shader(Util::read_data_file("shaders/vkcube.frag.spv")) .set_vertex_input(mesh->binding_descriptions(), mesh->attribute_descriptions()) .build(); } void CubeScene::setup_framebuffers(std::vector const& vulkan_images) { for (auto const& vulkan_image : vulkan_images) { image_views.push_back( vkutil::ImageViewBuilder{*vulkan} .set_image(vulkan_image.image) .set_format(vulkan_image.format) .set_aspect_mask(vk::ImageAspectFlagBits::eColor) .build()); } for (auto const& image_view : image_views) { framebuffers.push_back( vkutil::FramebufferBuilder{*vulkan} .set_render_pass(render_pass) .set_image_views({image_view}) .set_extent(extent) .build()); } } void CubeScene::setup_command_buffers() { auto const command_buffer_allocate_info = vk::CommandBufferAllocateInfo{} .setCommandPool(vulkan->command_pool()) .setCommandBufferCount(framebuffers.size()) .setLevel(vk::CommandBufferLevel::ePrimary); command_buffers = vulkan->device().allocateCommandBuffers(command_buffer_allocate_info); auto const binding_offsets = mesh->vertex_data_binding_offsets(); for (size_t i = 0; i < command_buffers.size(); ++i) { auto const begin_info = vk::CommandBufferBeginInfo{} .setFlags(vk::CommandBufferUsageFlagBits::eSimultaneousUse); command_buffers[i].begin(begin_info); vk::ClearValue const clear_color{ vk::ClearColorValue{std::array{{0.2f, 0.2f, 0.2f, 1.0f}}}}; auto const render_pass_begin_info = vk::RenderPassBeginInfo{} .setRenderPass(render_pass) .setFramebuffer(framebuffers[i]) .setRenderArea({{0,0}, extent}) .setClearValueCount(1) .setPClearValues(&clear_color); command_buffers[i].beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); command_buffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); command_buffers[i].bindDescriptorSets( vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set.raw, {}); command_buffers[i].bindVertexBuffers( 0, std::vector{binding_offsets.size(), vertex_buffer.raw}, binding_offsets ); command_buffers[i].draw(mesh->num_vertices(), 1, 0, 0); command_buffers[i].endRenderPass(); command_buffers[i].end(); } } void CubeScene::update_uniforms() { Uniforms ubo; ubo.modelview = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -8.0f)); ubo.modelview = glm::rotate(ubo.modelview, glm::radians(rotation.x), {1.0f, 0.0f, 0.0f}); ubo.modelview = glm::rotate(ubo.modelview, glm::radians(rotation.y), {0.0f, 1.0f, 0.0f}); ubo.modelview = glm::rotate(ubo.modelview, glm::radians(rotation.z), {0.0f, 0.0f, 1.0f}); auto const projection = glm::frustum(-2.8f, 2.8f, -2.8f * aspect, 2.8f * aspect, 6.0f, 10.0f); ubo.modelviewprojection = projection * ubo.modelview; ubo.normal = glm::inverseTranspose(ubo.modelview); memcpy(uniform_buffer_map, &ubo, sizeof(ubo)); } vkmark-2017.08+git20220909/src/scenes/cube_scene.h000066400000000000000000000045071441550741700211260ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "scene.h" #include "managed_resource.h" #include #define GLM_FORCE_DEPTH_ZERO_TO_ONE #include #include class Mesh; class CubeScene : public Scene { public: CubeScene(); ~CubeScene(); void setup(VulkanState&, std::vector const&) override; void teardown() override; VulkanImage draw(VulkanImage const&) override; void update() override; private: void setup_vertex_buffer(); void setup_uniform_buffer(); void setup_uniform_descriptor_set(); void setup_render_pass(); void setup_pipeline(); void setup_framebuffers(std::vector const&); void setup_command_buffers(); void update_uniforms(); VulkanState* vulkan; vk::Extent2D extent; vk::Format format; float aspect; std::unique_ptr mesh; ManagedResource vertex_buffer; ManagedResource uniform_buffer; ManagedResource uniform_buffer_map; ManagedResource descriptor_set; ManagedResource render_pass; ManagedResource pipeline_layout; ManagedResource pipeline; std::vector> image_views; std::vector> framebuffers; std::vector command_buffers; ManagedResource submit_semaphore; vk::DeviceMemory uniform_buffer_memory; vk::DescriptorSetLayout descriptor_set_layout; glm::vec3 rotation; }; vkmark-2017.08+git20220909/src/scenes/default_options_scene.cpp000066400000000000000000000023371441550741700237410ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "default_options_scene.h" #include "scene_collection.h" DefaultOptionsScene::DefaultOptionsScene(SceneCollection& scene_collection) : Scene{""}, scene_collection{scene_collection} { } void DefaultOptionsScene::setup(VulkanState&, std::vector const&) { for (auto const& kv : options_) { if (kv.second.set) scene_collection.set_option_default(kv.first, kv.second.value); } } vkmark-2017.08+git20220909/src/scenes/default_options_scene.h000066400000000000000000000021151441550741700234000ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "scene.h" class SceneCollection; class DefaultOptionsScene : public Scene { public: DefaultOptionsScene(SceneCollection& scene_collection); void setup(VulkanState&, std::vector const&) override; private: SceneCollection& scene_collection; }; vkmark-2017.08+git20220909/src/scenes/desktop_scene.cpp000066400000000000000000000316561441550741700222210ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "desktop_scene.h" #include "mesh.h" #include "util.h" #include "vulkan_state.h" #include "vulkan_image.h" #include "vkutil/vkutil.h" #include #include namespace { struct Uniforms { glm::mat4 transform; }; std::unique_ptr create_quad_mesh() { auto mesh = std::make_unique( std::vector{vk::Format::eR32G32Sfloat, vk::Format::eR32G32Sfloat}); mesh->next_vertex(); mesh->set_attribute(0, {-1,-1}); mesh->set_attribute(1, {0,0}); mesh->next_vertex(); mesh->set_attribute(0, {-1,1}); mesh->set_attribute(1, {0,1}); mesh->next_vertex(); mesh->set_attribute(0, {1,1}); mesh->set_attribute(1, {1,1}); mesh->next_vertex(); mesh->set_attribute(0, {-1,-1}); mesh->set_attribute(1, {0,0}); mesh->next_vertex(); mesh->set_attribute(0, {1,1}); mesh->set_attribute(1, {1,1}); mesh->next_vertex(); mesh->set_attribute(0, {1,-1}); mesh->set_attribute(1, {1,0}); mesh->set_interleave(true); return mesh; } } class DesktopScene::RenderObject { public: RenderObject(VulkanState& vulkan, std::string const& texture_file) : vulkan{vulkan} { texture = vkutil::TextureBuilder{vulkan} .set_file(texture_file) .set_filter(vk::Filter::eLinear) .build(); uniform_buffer = vkutil::BufferBuilder{vulkan} .set_size(sizeof(Uniforms)) .set_usage(vk::BufferUsageFlagBits::eUniformBuffer) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(uniform_buffer_memory) .build(); uniform_buffer_map = vkutil::map_memory( vulkan, uniform_buffer_memory, 0, sizeof(Uniforms)); descriptor_set = vkutil::DescriptorSetBuilder{vulkan} .set_type(vk::DescriptorType::eUniformBuffer) .set_stage_flags(vk::ShaderStageFlagBits::eVertex) .set_buffer(uniform_buffer, 0, sizeof(Uniforms)) .next_binding() .set_type(vk::DescriptorType::eCombinedImageSampler) .set_stage_flags(vk::ShaderStageFlagBits::eFragment) .set_image_view(texture.image_view, texture.sampler) .set_layout_out(descriptor_set_layout) .build(); } ~RenderObject() { descriptor_set = {}; uniform_buffer_map = {}; uniform_buffer = {}; texture = {}; } void update(float dt) { bool should_update = true; auto const new_pos = position + speed * dt; if (new_pos.x - size.x < -1.0f || new_pos.x + size.x > 1.0f) { speed.x = -speed.x; should_update = false; } if (new_pos.y - size.y < -1.0f || new_pos.y + size.y > 1.0f) { speed.y = -speed.y; should_update = false; } if (should_update) position = new_pos; } void update_uniforms() const { Uniforms ubo; ubo.transform = glm::translate(glm::mat4{1.0f}, glm::vec3{position.x, position.y, 0.0f}); ubo.transform = glm::scale(ubo.transform, glm::vec3{size.x, size.y, 1.0f}); memcpy(uniform_buffer_map, &ubo, sizeof(ubo)); } glm::vec2 position{0.0f, 0.0f}; glm::vec2 size{1.0f, 1.0f}; glm::vec2 speed{0.0f, 0.0f}; VulkanState& vulkan; vkutil::Texture texture; ManagedResource uniform_buffer; ManagedResource uniform_buffer_map; ManagedResource descriptor_set; vk::DescriptorSetLayout descriptor_set_layout; vk::DeviceMemory uniform_buffer_memory; }; DesktopScene::DesktopScene() : Scene{"desktop"} { options_["windows"] = SceneOption("windows", "4", "the number of windows"); options_["window-size"] = SceneOption("window-size", "0.35", "the window size as a percentage of the minimum screen dimension [0.0 - 0.5]"); options_["background-resolution"] = SceneOption("background-resolution", "800x600", "the resolution of the background image", "800x600,1920x1080"); } DesktopScene::~DesktopScene() = default; void DesktopScene::setup( VulkanState& vulkan_, std::vector const& vulkan_images) { Scene::setup(vulkan_, vulkan_images); vulkan = &vulkan_; extent = vulkan_images[0].extent; format = vulkan_images[0].format; mesh = create_quad_mesh(); auto const texture_file = "textures/desktop-background-" + options_["background-resolution"].value + ".png"; background = std::make_unique(*vulkan, texture_file); background->update_uniforms(); auto const aspect = static_cast(extent.width) / extent.height; auto const num_windows = Util::from_string(options_["windows"].value); auto const window_size_factor = Util::from_string(options_["window-size"].value); auto const window_size = glm::vec2{window_size_factor * (aspect > 1.0f ? 1.0 / aspect : 1.0f), window_size_factor * (aspect < 1.0f ? aspect : 1.0f)}; windows.resize(num_windows); for (auto i = 0u; i < windows.size(); ++i) { windows[i] = std::make_unique(*vulkan, "textures/desktop-window.png"); windows[i]->size = window_size; windows[i]->speed = {std::cos(0.1 + i * M_PI / 6.0) * 2.0 / 3, std::sin(0.1 + i * M_PI / 6.0) * 2.0 / 3}; } setup_vertex_buffer(); setup_render_pass(); setup_pipeline(); setup_framebuffers(vulkan_images); setup_command_buffers(); submit_semaphore = vkutil::SemaphoreBuilder{*vulkan}.build(); } void DesktopScene::teardown() { vulkan->device().waitIdle(); submit_semaphore = {}; vulkan->device().freeCommandBuffers(vulkan->command_pool(), command_buffers); framebuffers.clear(); image_views.clear(); pipeline_opaque = {}; pipeline_blend = {}; pipeline_layout = {}; render_pass = {}; vertex_buffer = {}; windows.clear(); background = {}; Scene::teardown(); } VulkanImage DesktopScene::draw(VulkanImage const& image) { update_uniforms(); vk::PipelineStageFlags const mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; auto const submit_info = vk::SubmitInfo{} .setCommandBufferCount(1) .setPCommandBuffers(&command_buffers[image.index]) .setWaitSemaphoreCount(image.semaphore ? 1 : 0) .setPWaitSemaphores(&image.semaphore) .setPWaitDstStageMask(&mask) .setSignalSemaphoreCount(1) .setPSignalSemaphores(&submit_semaphore.raw); vulkan->graphics_queue().submit(submit_info, {}); return image.copy_with_semaphore(submit_semaphore); } void DesktopScene::update() { auto const dt = (Util::get_timestamp_us() - last_update_time) / 1000000.0f; for (auto const& window : windows) window->update(dt); Scene::update(); } void DesktopScene::setup_vertex_buffer() { vk::DeviceMemory staging_buffer_memory; vk::BufferUsageFlags staging_usage_flags = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferSrc; auto staging_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage(staging_usage_flags) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(staging_buffer_memory) .build(); { auto const staging_buffer_map = vkutil::map_memory( *vulkan, staging_buffer_memory, 0, mesh->vertex_data_size()); mesh->copy_vertex_data_to(staging_buffer_map); } vertex_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage( vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst) .set_memory_properties(vk::MemoryPropertyFlagBits::eDeviceLocal) .build(); vkutil::copy_buffer(*vulkan, staging_buffer, vertex_buffer, mesh->vertex_data_size()); } void DesktopScene::setup_render_pass() { render_pass = vkutil::RenderPassBuilder(*vulkan) .set_color_format(format) .set_color_load_op(vk::AttachmentLoadOp::eDontCare) .build(); } void DesktopScene::setup_pipeline() { auto const pipeline_layout_create_info = vk::PipelineLayoutCreateInfo{} .setSetLayoutCount(1) .setPSetLayouts(&windows.front()->descriptor_set_layout); pipeline_layout = ManagedResource{ vulkan->device().createPipelineLayout(pipeline_layout_create_info), [this] (auto const& pl) { vulkan->device().destroyPipelineLayout(pl); }}; vkutil::PipelineBuilder pipeline_builder{*vulkan}; pipeline_builder .set_extent(extent) .set_layout(pipeline_layout) .set_render_pass(render_pass) .set_vertex_shader(Util::read_data_file("shaders/desktop.vert.spv")) .set_fragment_shader(Util::read_data_file("shaders/desktop.frag.spv")) .set_vertex_input(mesh->binding_descriptions(), mesh->attribute_descriptions()); pipeline_opaque = pipeline_builder.build(); pipeline_blend = pipeline_builder.set_blend(true).build(); } void DesktopScene::setup_framebuffers(std::vector const& vulkan_images) { for (auto const& vulkan_image : vulkan_images) { image_views.push_back( vkutil::ImageViewBuilder{*vulkan} .set_image(vulkan_image.image) .set_format(vulkan_image.format) .set_aspect_mask(vk::ImageAspectFlagBits::eColor) .build()); } for (auto const& image_view : image_views) { framebuffers.push_back( vkutil::FramebufferBuilder{*vulkan} .set_render_pass(render_pass) .set_image_views({image_view}) .set_extent(extent) .build()); } } void DesktopScene::setup_command_buffers() { auto const command_buffer_allocate_info = vk::CommandBufferAllocateInfo{} .setCommandPool(vulkan->command_pool()) .setCommandBufferCount(framebuffers.size()) .setLevel(vk::CommandBufferLevel::ePrimary); command_buffers = vulkan->device().allocateCommandBuffers(command_buffer_allocate_info); auto const binding_offsets = mesh->vertex_data_binding_offsets(); for (size_t i = 0; i < command_buffers.size(); ++i) { auto const begin_info = vk::CommandBufferBeginInfo{} .setFlags(vk::CommandBufferUsageFlagBits::eSimultaneousUse); command_buffers[i].begin(begin_info); auto const render_pass_begin_info = vk::RenderPassBeginInfo{} .setRenderPass(render_pass) .setFramebuffer(framebuffers[i]) .setRenderArea({{0,0}, extent}); command_buffers[i].beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); command_buffers[i].bindVertexBuffers( 0, std::vector{binding_offsets.size(), vertex_buffer.raw}, binding_offsets ); // Draw background opaquely command_buffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline_opaque); command_buffers[i].bindDescriptorSets( vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, background->descriptor_set.raw, {}); command_buffers[i].draw(mesh->num_vertices(), 1, 0, 0); // Draw windows with blending command_buffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline_blend); for (auto const& window : windows) { command_buffers[i].bindDescriptorSets( vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, window->descriptor_set.raw, {}); command_buffers[i].draw(mesh->num_vertices(), 1, 0, 0); } command_buffers[i].endRenderPass(); command_buffers[i].end(); } } void DesktopScene::update_uniforms() { for (auto const& window : windows) window->update_uniforms(); } vkmark-2017.08+git20220909/src/scenes/desktop_scene.h000066400000000000000000000042351441550741700216570ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "scene.h" #include "managed_resource.h" #include #define GLM_FORCE_DEPTH_ZERO_TO_ONE #include #include class Mesh; class DesktopScene : public Scene { public: DesktopScene(); ~DesktopScene(); void setup(VulkanState&, std::vector const&) override; void teardown() override; VulkanImage draw(VulkanImage const&) override; void update() override; private: class RenderObject; void setup_vertex_buffer(); void setup_render_pass(); void setup_pipeline(); void setup_framebuffers(std::vector const&); void setup_command_buffers(); void update_uniforms(); VulkanState* vulkan; vk::Extent2D extent; vk::Format format; std::unique_ptr mesh; std::unique_ptr background; std::vector> windows; ManagedResource vertex_buffer; ManagedResource render_pass; ManagedResource pipeline_layout; ManagedResource pipeline_opaque; ManagedResource pipeline_blend; std::vector> image_views; std::vector> framebuffers; std::vector command_buffers; ManagedResource submit_semaphore; }; vkmark-2017.08+git20220909/src/scenes/effect2d_scene.cpp000066400000000000000000000240321441550741700222200ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "effect2d_scene.h" #include "mesh.h" #include "util.h" #include "vulkan_state.h" #include "vulkan_image.h" #include "vkutil/vkutil.h" #include #include namespace { struct Uniforms { float texture_step_x; float texture_step_y; }; std::unique_ptr create_quad_mesh() { auto mesh = std::make_unique( std::vector{vk::Format::eR32G32Sfloat, vk::Format::eR32G32Sfloat}); mesh->next_vertex(); mesh->set_attribute(0, {-1,-1}); mesh->set_attribute(1, {0,0}); mesh->next_vertex(); mesh->set_attribute(0, {-1,1}); mesh->set_attribute(1, {0,1}); mesh->next_vertex(); mesh->set_attribute(0, {1,1}); mesh->set_attribute(1, {1,1}); mesh->next_vertex(); mesh->set_attribute(0, {-1,-1}); mesh->set_attribute(1, {0,0}); mesh->next_vertex(); mesh->set_attribute(0, {1,1}); mesh->set_attribute(1, {1,1}); mesh->next_vertex(); mesh->set_attribute(0, {1,-1}); mesh->set_attribute(1, {1,0}); mesh->set_interleave(true); return mesh; } } Effect2DScene::Effect2DScene() : Scene{"effect2d"} { options_["kernel"] = SceneOption("kernel", "blur", "the convolution kernel to use", "blur,edge,none"); options_["background-resolution"] = SceneOption("background-resolution", "800x600", "the resolution of the background image", "800x600,1920x1080"); } Effect2DScene::~Effect2DScene() = default; void Effect2DScene::setup( VulkanState& vulkan_, std::vector const& vulkan_images) { Scene::setup(vulkan_, vulkan_images); vulkan = &vulkan_; extent = vulkan_images[0].extent; format = vulkan_images[0].format; mesh = create_quad_mesh(); setup_vertex_buffer(); setup_uniform_buffer(); setup_texture(); setup_shader_descriptor_set(); setup_render_pass(); setup_pipeline(); setup_framebuffers(vulkan_images); setup_command_buffers(); update_uniforms(); submit_semaphore = vkutil::SemaphoreBuilder{*vulkan}.build(); } void Effect2DScene::teardown() { vulkan->device().waitIdle(); submit_semaphore = {}; vulkan->device().freeCommandBuffers(vulkan->command_pool(), command_buffers); framebuffers.clear(); image_views.clear(); pipeline = {}; pipeline_layout = {}; render_pass = {}; descriptor_set = {}; texture = {}; uniform_buffer_map = {}; uniform_buffer = {}; vertex_buffer = {}; Scene::teardown(); } VulkanImage Effect2DScene::draw(VulkanImage const& image) { vk::PipelineStageFlags const mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; auto const submit_info = vk::SubmitInfo{} .setCommandBufferCount(1) .setPCommandBuffers(&command_buffers[image.index]) .setWaitSemaphoreCount(image.semaphore ? 1 : 0) .setPWaitSemaphores(&image.semaphore) .setPWaitDstStageMask(&mask) .setSignalSemaphoreCount(1) .setPSignalSemaphores(&submit_semaphore.raw); vulkan->graphics_queue().submit(submit_info, {}); return image.copy_with_semaphore(submit_semaphore); } void Effect2DScene::update() { Scene::update(); } void Effect2DScene::setup_vertex_buffer() { vk::DeviceMemory staging_buffer_memory; vk::BufferUsageFlags staging_usage_flags = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferSrc; auto staging_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage(staging_usage_flags) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(staging_buffer_memory) .build(); { auto const staging_buffer_map = vkutil::map_memory( *vulkan, staging_buffer_memory, 0, mesh->vertex_data_size()); mesh->copy_vertex_data_to(staging_buffer_map); } vertex_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage( vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst) .set_memory_properties(vk::MemoryPropertyFlagBits::eDeviceLocal) .build(); vkutil::copy_buffer(*vulkan, staging_buffer, vertex_buffer, mesh->vertex_data_size()); } void Effect2DScene::setup_uniform_buffer() { uniform_buffer = vkutil::BufferBuilder{*vulkan} .set_size(sizeof(Uniforms)) .set_usage(vk::BufferUsageFlagBits::eUniformBuffer) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(uniform_buffer_memory) .build(); uniform_buffer_map = vkutil::map_memory( *vulkan, uniform_buffer_memory, 0, sizeof(Uniforms)); } void Effect2DScene::setup_texture() { auto const texture_file = "textures/desktop-background-" + options_["background-resolution"].value + ".png"; texture = vkutil::TextureBuilder{*vulkan} .set_file(texture_file) .set_filter(vk::Filter::eNearest) .build(); } void Effect2DScene::setup_shader_descriptor_set() { descriptor_set = vkutil::DescriptorSetBuilder{*vulkan} .set_type(vk::DescriptorType::eUniformBuffer) .set_stage_flags(vk::ShaderStageFlagBits::eFragment) .set_buffer(uniform_buffer, 0, sizeof(Uniforms)) .next_binding() .set_type(vk::DescriptorType::eCombinedImageSampler) .set_stage_flags(vk::ShaderStageFlagBits::eFragment) .set_image_view(texture.image_view, texture.sampler) .set_layout_out(descriptor_set_layout) .build(); } void Effect2DScene::setup_render_pass() { render_pass = vkutil::RenderPassBuilder(*vulkan) .set_color_format(format) .set_color_load_op(vk::AttachmentLoadOp::eDontCare) .build(); } void Effect2DScene::setup_pipeline() { auto const pipeline_layout_create_info = vk::PipelineLayoutCreateInfo{} .setSetLayoutCount(1) .setPSetLayouts(&descriptor_set_layout); pipeline_layout = ManagedResource{ vulkan->device().createPipelineLayout(pipeline_layout_create_info), [this] (auto const& pl) { vulkan->device().destroyPipelineLayout(pl); }}; auto const frag_shader_file = "shaders/effect2d-" + options_["kernel"].value + ".frag.spv"; pipeline = vkutil::PipelineBuilder{*vulkan} .set_extent(extent) .set_layout(pipeline_layout) .set_render_pass(render_pass) .set_vertex_shader(Util::read_data_file("shaders/effect2d.vert.spv")) .set_fragment_shader(Util::read_data_file(frag_shader_file)) .set_vertex_input(mesh->binding_descriptions(), mesh->attribute_descriptions()) .build(); } void Effect2DScene::setup_framebuffers(std::vector const& vulkan_images) { for (auto const& vulkan_image : vulkan_images) { image_views.push_back( vkutil::ImageViewBuilder{*vulkan} .set_image(vulkan_image.image) .set_format(vulkan_image.format) .set_aspect_mask(vk::ImageAspectFlagBits::eColor) .build()); } for (auto const& image_view : image_views) { framebuffers.push_back( vkutil::FramebufferBuilder{*vulkan} .set_render_pass(render_pass) .set_image_views({image_view}) .set_extent(extent) .build()); } } void Effect2DScene::setup_command_buffers() { auto const command_buffer_allocate_info = vk::CommandBufferAllocateInfo{} .setCommandPool(vulkan->command_pool()) .setCommandBufferCount(framebuffers.size()) .setLevel(vk::CommandBufferLevel::ePrimary); command_buffers = vulkan->device().allocateCommandBuffers(command_buffer_allocate_info); auto const binding_offsets = mesh->vertex_data_binding_offsets(); for (size_t i = 0; i < command_buffers.size(); ++i) { auto const begin_info = vk::CommandBufferBeginInfo{} .setFlags(vk::CommandBufferUsageFlagBits::eSimultaneousUse); command_buffers[i].begin(begin_info); auto const render_pass_begin_info = vk::RenderPassBeginInfo{} .setRenderPass(render_pass) .setFramebuffer(framebuffers[i]) .setRenderArea({{0,0}, extent}); command_buffers[i].beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); command_buffers[i].bindVertexBuffers( 0, std::vector{binding_offsets.size(), vertex_buffer.raw}, binding_offsets ); command_buffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); command_buffers[i].bindDescriptorSets( vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set.raw, {}); command_buffers[i].draw(mesh->num_vertices(), 1, 0, 0); command_buffers[i].endRenderPass(); command_buffers[i].end(); } } void Effect2DScene::update_uniforms() { Uniforms ubo; ubo.texture_step_x = 1.0 / extent.width; ubo.texture_step_y = 1.0 / extent.height; memcpy(uniform_buffer_map, &ubo, sizeof(ubo)); } vkmark-2017.08+git20220909/src/scenes/effect2d_scene.h000066400000000000000000000045721441550741700216740ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "scene.h" #include "managed_resource.h" #include "vkutil/texture.h" #include #define GLM_FORCE_DEPTH_ZERO_TO_ONE #include #include class Mesh; class Effect2DScene : public Scene { public: Effect2DScene(); ~Effect2DScene(); void setup(VulkanState&, std::vector const&) override; void teardown() override; VulkanImage draw(VulkanImage const&) override; void update() override; private: void setup_vertex_buffer(); void setup_uniform_buffer(); void setup_texture(); void setup_shader_descriptor_set(); void setup_render_pass(); void setup_pipeline(); void setup_framebuffers(std::vector const&); void setup_command_buffers(); void update_uniforms(); VulkanState* vulkan; vk::Extent2D extent; vk::Format format; std::unique_ptr mesh; ManagedResource vertex_buffer; ManagedResource uniform_buffer; ManagedResource uniform_buffer_map; vkutil::Texture texture; ManagedResource descriptor_set; ManagedResource render_pass; ManagedResource pipeline_layout; ManagedResource pipeline; std::vector> image_views; std::vector> framebuffers; std::vector command_buffers; ManagedResource submit_semaphore; vk::DeviceMemory uniform_buffer_memory; vk::DescriptorSetLayout descriptor_set_layout; }; vkmark-2017.08+git20220909/src/scenes/shading_scene.cpp000066400000000000000000000276241441550741700221650ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "shading_scene.h" #include "mesh.h" #include "model.h" #include "util.h" #include "vulkan_state.h" #include "vulkan_image.h" #include "vkutil/vkutil.h" #include #include #include namespace { struct Uniforms { glm::mat4 modelviewprojection; glm::mat4 normal; glm::vec4 material_diffuse; glm::mat4 modelview; }; } ShadingScene::ShadingScene() : Scene{"shading"} { options_["shading"] = SceneOption("shading", "gouraud", "Which shading method to use", "gouraud,blinn-phong-inf,phong,cel"); } ShadingScene::~ShadingScene() = default; void ShadingScene::setup( VulkanState& vulkan_, std::vector const& vulkan_images) { Scene::setup(vulkan_, vulkan_images); vulkan = &vulkan_; extent = vulkan_images[0].extent; format = vulkan_images[0].format; depth_format = vk::Format::eD32Sfloat; aspect = static_cast(extent.height) / extent.width; mesh = Model{"cat.3ds"}.to_mesh( ModelAttribMap{} .with_position(vk::Format::eR32G32B32Sfloat) .with_normal(vk::Format::eR32G32B32Sfloat)); mesh->set_interleave(true); // Model projection auto const min_bound = mesh->min_attribute_bound(0); auto const max_bound = mesh->max_attribute_bound(0); auto const diameter = glm::length(max_bound - min_bound); auto const aspect = static_cast(extent.width)/static_cast(extent.height); center = (max_bound + min_bound) / 2.0f; radius = diameter / 2.0f; auto const fovy = 2.0f * atanf(radius / (2.0f + radius)); projection = glm::perspective(fovy, aspect, 2.0f, 2.0f + diameter); setup_vertex_buffer(); setup_uniform_buffer(); setup_uniform_descriptor_set(); setup_render_pass(); setup_pipeline(); setup_depth_image(); setup_framebuffers(vulkan_images); setup_command_buffers(); submit_semaphore = vkutil::SemaphoreBuilder{*vulkan}.build(); rotation = 0.0; } void ShadingScene::teardown() { vulkan->device().waitIdle(); submit_semaphore = {}; vulkan->device().freeCommandBuffers(vulkan->command_pool(), command_buffers); framebuffers.clear(); image_views.clear(); depth_image_view = {}; depth_image = {}; pipeline = {}; pipeline_layout = {}; render_pass = {}; descriptor_set = {}; uniform_buffer_map = {}; uniform_buffer = {}; vertex_buffer = {}; Scene::teardown(); } VulkanImage ShadingScene::draw(VulkanImage const& image) { update_uniforms(); vk::PipelineStageFlags const mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; auto const submit_info = vk::SubmitInfo{} .setCommandBufferCount(1) .setPCommandBuffers(&command_buffers[image.index]) .setWaitSemaphoreCount(image.semaphore ? 1 : 0) .setPWaitSemaphores(&image.semaphore) .setPWaitDstStageMask(&mask) .setSignalSemaphoreCount(1) .setPSignalSemaphores(&submit_semaphore.raw); vulkan->graphics_queue().submit(submit_info, {}); return image.copy_with_semaphore(submit_semaphore); } void ShadingScene::update() { auto const t = (Util::get_timestamp_us() - start_time) / 1000000.0f; rotation = 36.0f * t; Scene::update(); } void ShadingScene::setup_vertex_buffer() { vk::DeviceMemory staging_buffer_memory; vk::BufferUsageFlags staging_usage_flags = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferSrc; auto staging_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage(staging_usage_flags) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(staging_buffer_memory) .build(); { auto const staging_buffer_map = vkutil::map_memory( *vulkan, staging_buffer_memory, 0, mesh->vertex_data_size()); mesh->copy_vertex_data_to(staging_buffer_map); } vertex_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage( vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst) .set_memory_properties(vk::MemoryPropertyFlagBits::eDeviceLocal) .build(); vkutil::copy_buffer(*vulkan, staging_buffer, vertex_buffer, mesh->vertex_data_size()); } void ShadingScene::setup_uniform_buffer() { uniform_buffer = vkutil::BufferBuilder{*vulkan} .set_size(sizeof(Uniforms)) .set_usage(vk::BufferUsageFlagBits::eUniformBuffer) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(uniform_buffer_memory) .build(); uniform_buffer_map = vkutil::map_memory( *vulkan, uniform_buffer_memory, 0, sizeof(Uniforms)); } void ShadingScene::setup_uniform_descriptor_set() { descriptor_set = vkutil::DescriptorSetBuilder{*vulkan} .set_type(vk::DescriptorType::eUniformBuffer) .set_stage_flags(vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment) .set_buffer(uniform_buffer, 0, sizeof(Uniforms)) .set_layout_out(descriptor_set_layout) .build(); } void ShadingScene::setup_render_pass() { render_pass = vkutil::RenderPassBuilder(*vulkan) .set_color_format(format) .set_depth_format(depth_format) .set_color_load_op(vk::AttachmentLoadOp::eClear) .build(); } void ShadingScene::setup_pipeline() { auto const pipeline_layout_create_info = vk::PipelineLayoutCreateInfo{} .setSetLayoutCount(1) .setPSetLayouts(&descriptor_set_layout); pipeline_layout = ManagedResource{ vulkan->device().createPipelineLayout(pipeline_layout_create_info), [this] (auto const& pl) { vulkan->device().destroyPipelineLayout(pl); }}; std::vector vertex_shader; std::vector fragment_shader; if (options_["shading"].value == "gouraud") { vertex_shader = Util::read_data_file("shaders/light-basic.vert.spv"); fragment_shader = Util::read_data_file("shaders/light-basic.frag.spv"); } else if (options_["shading"].value == "blinn-phong-inf") { vertex_shader = Util::read_data_file("shaders/light-advanced.vert.spv"); fragment_shader = Util::read_data_file("shaders/light-advanced.frag.spv"); } else if (options_["shading"].value == "phong") { vertex_shader = Util::read_data_file("shaders/light-phong.vert.spv"); fragment_shader = Util::read_data_file("shaders/light-phong.frag.spv"); } else if (options_["shading"].value == "cel") { vertex_shader = Util::read_data_file("shaders/light-phong.vert.spv"); fragment_shader = Util::read_data_file("shaders/light-cel.frag.spv"); } pipeline = vkutil::PipelineBuilder(*vulkan) .set_extent(extent) .set_layout(pipeline_layout) .set_render_pass(render_pass) .set_vertex_shader(vertex_shader) .set_fragment_shader(fragment_shader) .set_vertex_input(mesh->binding_descriptions(), mesh->attribute_descriptions()) .set_depth_test(true) .build(); } void ShadingScene::setup_depth_image() { depth_image = vkutil::ImageBuilder{*vulkan} .set_extent(extent) .set_format(depth_format) .set_tiling(vk::ImageTiling::eOptimal) .set_usage(vk::ImageUsageFlagBits::eDepthStencilAttachment) .set_memory_properties(vk::MemoryPropertyFlagBits::eDeviceLocal) .set_initial_layout(vk::ImageLayout::eUndefined) .build(); vkutil::transition_image_layout( *vulkan, depth_image, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal, vk::ImageAspectFlagBits::eDepth); } void ShadingScene::setup_framebuffers(std::vector const& vulkan_images) { depth_image_view = vkutil::ImageViewBuilder{*vulkan} .set_image(depth_image) .set_format(depth_format) .set_aspect_mask(vk::ImageAspectFlagBits::eDepth) .build(); for (auto const& vulkan_image : vulkan_images) { image_views.push_back( vkutil::ImageViewBuilder{*vulkan} .set_image(vulkan_image.image) .set_format(vulkan_image.format) .set_aspect_mask(vk::ImageAspectFlagBits::eColor) .build()); } for (auto const& image_view : image_views) { framebuffers.push_back( vkutil::FramebufferBuilder{*vulkan} .set_render_pass(render_pass) .set_image_views({image_view, depth_image_view}) .set_extent(extent) .build()); } } void ShadingScene::setup_command_buffers() { auto const command_buffer_allocate_info = vk::CommandBufferAllocateInfo{} .setCommandPool(vulkan->command_pool()) .setCommandBufferCount(framebuffers.size()) .setLevel(vk::CommandBufferLevel::ePrimary); command_buffers = vulkan->device().allocateCommandBuffers(command_buffer_allocate_info); auto const binding_offsets = mesh->vertex_data_binding_offsets(); for (size_t i = 0; i < command_buffers.size(); ++i) { auto const begin_info = vk::CommandBufferBeginInfo{} .setFlags(vk::CommandBufferUsageFlagBits::eSimultaneousUse); command_buffers[i].begin(begin_info); std::array clear_values{{ vk::ClearColorValue{std::array{{0.0f, 0.0f, 0.0f, 1.0f}}}, vk::ClearDepthStencilValue{1.0f, 0}}}; auto const render_pass_begin_info = vk::RenderPassBeginInfo{} .setRenderPass(render_pass) .setFramebuffer(framebuffers[i]) .setRenderArea({{0,0}, extent}) .setClearValueCount(clear_values.size()) .setPClearValues(clear_values.data()); command_buffers[i].beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); command_buffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); command_buffers[i].bindDescriptorSets( vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set.raw, {}); command_buffers[i].bindVertexBuffers( 0, std::vector{binding_offsets.size(), vertex_buffer.raw}, binding_offsets ); command_buffers[i].draw(mesh->num_vertices(), 1, 0, 0); command_buffers[i].endRenderPass(); command_buffers[i].end(); } } void ShadingScene::update_uniforms() { Uniforms ubo; glm::mat4 modelview{1.0}; modelview = glm::translate(modelview, glm::vec3{-center.x, -center.y, -(center.z + 2.0 + radius)}); modelview = glm::rotate(modelview, glm::radians(rotation), {0.0f, 1.0f, 0.0f}); ubo.modelviewprojection = projection * modelview; ubo.normal = glm::inverseTranspose(modelview); ubo.material_diffuse = glm::vec4{0.0f, 0.0f, 0.7f, 1.0f}; ubo.modelview = modelview; memcpy(uniform_buffer_map, &ubo, sizeof(ubo)); } vkmark-2017.08+git20220909/src/scenes/shading_scene.h000066400000000000000000000050521441550741700216210ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "scene.h" #include "managed_resource.h" #include #define GLM_FORCE_DEPTH_ZERO_TO_ONE #include #include class Mesh; class ShadingScene : public Scene { public: ShadingScene(); ~ShadingScene(); void setup(VulkanState&, std::vector const&) override; void teardown() override; VulkanImage draw(VulkanImage const&) override; void update() override; private: void setup_vertex_buffer(); void setup_uniform_buffer(); void setup_uniform_descriptor_set(); void setup_render_pass(); void setup_pipeline(); void setup_depth_image(); void setup_framebuffers(std::vector const&); void setup_command_buffers(); void update_uniforms(); VulkanState* vulkan; vk::Extent2D extent; vk::Format format; vk::Format depth_format; float aspect; glm::mat4 projection; glm::vec3 center; float radius; std::unique_ptr mesh; ManagedResource vertex_buffer; ManagedResource uniform_buffer; ManagedResource uniform_buffer_map; ManagedResource descriptor_set; ManagedResource render_pass; ManagedResource pipeline_layout; ManagedResource pipeline; ManagedResource depth_image; ManagedResource depth_image_view; std::vector> image_views; std::vector> framebuffers; std::vector command_buffers; ManagedResource submit_semaphore; vk::DeviceMemory uniform_buffer_memory; vk::DescriptorSetLayout descriptor_set_layout; float rotation; }; vkmark-2017.08+git20220909/src/scenes/texture_scene.cpp000066400000000000000000000301551441550741700222410ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "texture_scene.h" #include "mesh.h" #include "model.h" #include "util.h" #include "vulkan_state.h" #include "vulkan_image.h" #include "vkutil/vkutil.h" #include #include #include namespace { struct Uniforms { glm::mat4 modelviewprojection; glm::mat4 normal; glm::vec4 material_diffuse; }; } TextureScene::TextureScene() : Scene{"texture"} { options_["texture-filter"] = SceneOption("texture-filter", "linear", "The texture filter to use", "nearest,linear"); options_["anisotropy"] = SceneOption("anisotropy", "16", "The max anisotropy bound to use (use 0 to disable it)"); } TextureScene::~TextureScene() = default; void TextureScene::setup( VulkanState& vulkan_, std::vector const& vulkan_images) { Scene::setup(vulkan_, vulkan_images); vulkan = &vulkan_; extent = vulkan_images[0].extent; format = vulkan_images[0].format; depth_format = vk::Format::eD32Sfloat; aspect = static_cast(extent.height) / extent.width; mesh = Model{"cube.3ds"}.to_mesh( ModelAttribMap{} .with_position(vk::Format::eR32G32B32Sfloat) .with_normal(vk::Format::eR32G32B32Sfloat) .with_texcoord(vk::Format::eR32G32Sfloat)); mesh->set_interleave(true); // Model projection auto const min_bound = mesh->min_attribute_bound(0); auto const max_bound = mesh->max_attribute_bound(0); auto const diameter = glm::length(max_bound - min_bound); auto const aspect = static_cast(extent.width)/static_cast(extent.height); center = (max_bound + min_bound) / 2.0f; radius = diameter / 2.0f; auto const fovy = 2.0f * atanf(radius / (2.0f + radius)); projection = glm::perspective(fovy, aspect, 2.0f, 2.0f + diameter); setup_vertex_buffer(); setup_uniform_buffer(); setup_texture(); setup_shader_descriptor_set(); setup_render_pass(); setup_pipeline(); setup_depth_image(); setup_framebuffers(vulkan_images); setup_command_buffers(); submit_semaphore = vkutil::SemaphoreBuilder{*vulkan}.build(); rotation = 0.0f; } void TextureScene::teardown() { vulkan->device().waitIdle(); submit_semaphore = {}; vulkan->device().freeCommandBuffers(vulkan->command_pool(), command_buffers); framebuffers.clear(); image_views.clear(); depth_image_view = {}; depth_image = {}; pipeline = {}; pipeline_layout = {}; render_pass = {}; descriptor_set = {}; texture = {}; uniform_buffer_map = {}; uniform_buffer = {}; vertex_buffer = {}; Scene::teardown(); } VulkanImage TextureScene::draw(VulkanImage const& image) { update_uniforms(); vk::PipelineStageFlags const mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; auto const submit_info = vk::SubmitInfo{} .setCommandBufferCount(1) .setPCommandBuffers(&command_buffers[image.index]) .setWaitSemaphoreCount(image.semaphore ? 1 : 0) .setPWaitSemaphores(&image.semaphore) .setPWaitDstStageMask(&mask) .setSignalSemaphoreCount(1) .setPSignalSemaphores(&submit_semaphore.raw); vulkan->graphics_queue().submit(submit_info, {}); return image.copy_with_semaphore(submit_semaphore); } void TextureScene::update() { auto const t = (Util::get_timestamp_us() - start_time) / 1000000.0f; rotation = 36.0f * t; Scene::update(); } void TextureScene::setup_vertex_buffer() { vk::DeviceMemory staging_buffer_memory; vk::BufferUsageFlags staging_usage_flags = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferSrc; auto staging_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage(staging_usage_flags) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(staging_buffer_memory) .build(); { auto const staging_buffer_map = vkutil::map_memory( *vulkan, staging_buffer_memory, 0, mesh->vertex_data_size()); mesh->copy_vertex_data_to(staging_buffer_map); } vertex_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage( vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst) .set_memory_properties(vk::MemoryPropertyFlagBits::eDeviceLocal) .build(); vkutil::copy_buffer(*vulkan, staging_buffer, vertex_buffer, mesh->vertex_data_size()); } void TextureScene::setup_uniform_buffer() { uniform_buffer = vkutil::BufferBuilder{*vulkan} .set_size(sizeof(Uniforms)) .set_usage(vk::BufferUsageFlagBits::eUniformBuffer) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(uniform_buffer_memory) .build(); uniform_buffer_map = vkutil::map_memory( *vulkan, uniform_buffer_memory, 0, sizeof(Uniforms)); } void TextureScene::setup_texture() { auto const& filter = options_["texture-filter"].value; auto const anisotropy = std::stof(options_["anisotropy"].value); vk::Filter vk_filter = vk::Filter::eLinear; if (filter == "nearest") vk_filter = vk::Filter::eNearest; else if (filter == "linear") vk_filter = vk::Filter::eLinear; texture = vkutil::TextureBuilder{*vulkan} .set_file("textures/crate-base.jpg") .set_filter(vk_filter) .set_anisotropy(anisotropy) .build(); } void TextureScene::setup_shader_descriptor_set() { descriptor_set = vkutil::DescriptorSetBuilder{*vulkan} .set_type(vk::DescriptorType::eUniformBuffer) .set_stage_flags(vk::ShaderStageFlagBits::eVertex) .set_buffer(uniform_buffer, 0, sizeof(Uniforms)) .next_binding() .set_type(vk::DescriptorType::eCombinedImageSampler) .set_stage_flags(vk::ShaderStageFlagBits::eFragment) .set_image_view(texture.image_view, texture.sampler) .set_layout_out(descriptor_set_layout) .build(); } void TextureScene::setup_render_pass() { render_pass = vkutil::RenderPassBuilder(*vulkan) .set_color_format(format) .set_depth_format(depth_format) .set_color_load_op(vk::AttachmentLoadOp::eClear) .build(); } void TextureScene::setup_pipeline() { auto const pipeline_layout_create_info = vk::PipelineLayoutCreateInfo{} .setSetLayoutCount(1) .setPSetLayouts(&descriptor_set_layout); pipeline_layout = ManagedResource{ vulkan->device().createPipelineLayout(pipeline_layout_create_info), [this] (auto const& pl) { vulkan->device().destroyPipelineLayout(pl); }}; pipeline = vkutil::PipelineBuilder(*vulkan) .set_extent(extent) .set_layout(pipeline_layout) .set_render_pass(render_pass) .set_vertex_shader(Util::read_data_file("shaders/light-basic-tex.vert.spv")) .set_fragment_shader(Util::read_data_file("shaders/light-basic-tex.frag.spv")) .set_vertex_input(mesh->binding_descriptions(), mesh->attribute_descriptions()) .set_depth_test(true) .build(); } void TextureScene::setup_depth_image() { depth_image = vkutil::ImageBuilder{*vulkan} .set_extent(extent) .set_format(depth_format) .set_tiling(vk::ImageTiling::eOptimal) .set_usage(vk::ImageUsageFlagBits::eDepthStencilAttachment) .set_memory_properties(vk::MemoryPropertyFlagBits::eDeviceLocal) .set_initial_layout(vk::ImageLayout::eUndefined) .build(); vkutil::transition_image_layout( *vulkan, depth_image, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal, vk::ImageAspectFlagBits::eDepth); } void TextureScene::setup_framebuffers(std::vector const& vulkan_images) { depth_image_view = vkutil::ImageViewBuilder{*vulkan} .set_image(depth_image) .set_format(depth_format) .set_aspect_mask(vk::ImageAspectFlagBits::eDepth) .build(); for (auto const& vulkan_image : vulkan_images) { image_views.push_back( vkutil::ImageViewBuilder{*vulkan} .set_image(vulkan_image.image) .set_format(vulkan_image.format) .set_aspect_mask(vk::ImageAspectFlagBits::eColor) .build()); } for (auto const& image_view : image_views) { framebuffers.push_back( vkutil::FramebufferBuilder{*vulkan} .set_render_pass(render_pass) .set_image_views({image_view, depth_image_view}) .set_extent(extent) .build()); } } void TextureScene::setup_command_buffers() { auto const command_buffer_allocate_info = vk::CommandBufferAllocateInfo{} .setCommandPool(vulkan->command_pool()) .setCommandBufferCount(framebuffers.size()) .setLevel(vk::CommandBufferLevel::ePrimary); command_buffers = vulkan->device().allocateCommandBuffers(command_buffer_allocate_info); auto const binding_offsets = mesh->vertex_data_binding_offsets(); for (size_t i = 0; i < command_buffers.size(); ++i) { auto const begin_info = vk::CommandBufferBeginInfo{} .setFlags(vk::CommandBufferUsageFlagBits::eSimultaneousUse); command_buffers[i].begin(begin_info); std::array clear_values{{ vk::ClearColorValue{std::array{{0.0f, 0.0f, 0.0f, 1.0f}}}, vk::ClearDepthStencilValue{1.0f, 0}}}; auto const render_pass_begin_info = vk::RenderPassBeginInfo{} .setRenderPass(render_pass) .setFramebuffer(framebuffers[i]) .setRenderArea({{0,0}, extent}) .setClearValueCount(clear_values.size()) .setPClearValues(clear_values.data()); command_buffers[i].beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); command_buffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); command_buffers[i].bindDescriptorSets( vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set.raw, {}); command_buffers[i].bindVertexBuffers( 0, std::vector{binding_offsets.size(), vertex_buffer.raw}, binding_offsets ); command_buffers[i].draw(mesh->num_vertices(), 1, 0, 0); command_buffers[i].endRenderPass(); command_buffers[i].end(); } } void TextureScene::update_uniforms() { Uniforms ubo; glm::mat4 modelview{1.0}; modelview = glm::translate(modelview, glm::vec3{-center.x, center.y, -(center.z + 2.5 + radius)}); modelview = glm::rotate(modelview, glm::radians(rotation), {-1.0f, 0.0f, 0.0f}); modelview = glm::rotate(modelview, glm::radians(rotation), {0.0f, 1.0f, 0.0f}); modelview = glm::rotate(modelview, glm::radians(rotation), {0.0f, 0.0f, -1.0f}); ubo.modelviewprojection = projection * modelview; ubo.normal = glm::inverseTranspose(modelview); ubo.material_diffuse = glm::vec4{0.7f, 0.7f, 0.7f, 1.0f}; memcpy(uniform_buffer_map, &ubo, sizeof(ubo)); } vkmark-2017.08+git20220909/src/scenes/texture_scene.h000066400000000000000000000051741441550741700217110ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "scene.h" #include "managed_resource.h" #include "vkutil/texture.h" #include #define GLM_FORCE_DEPTH_ZERO_TO_ONE #include #include class Mesh; class TextureScene : public Scene { public: TextureScene(); ~TextureScene(); void setup(VulkanState&, std::vector const&) override; void teardown() override; VulkanImage draw(VulkanImage const&) override; void update() override; private: void setup_vertex_buffer(); void setup_uniform_buffer(); void setup_texture(); void setup_shader_descriptor_set(); void setup_render_pass(); void setup_pipeline(); void setup_depth_image(); void setup_framebuffers(std::vector const&); void setup_command_buffers(); void update_uniforms(); VulkanState* vulkan; vk::Extent2D extent; vk::Format format; vk::Format depth_format; float aspect; glm::mat4 projection; glm::vec3 center; float radius; std::unique_ptr mesh; ManagedResource vertex_buffer; ManagedResource uniform_buffer; ManagedResource uniform_buffer_map; vkutil::Texture texture; ManagedResource descriptor_set; ManagedResource render_pass; ManagedResource pipeline_layout; ManagedResource pipeline; ManagedResource depth_image; ManagedResource depth_image_view; std::vector> image_views; std::vector> framebuffers; std::vector command_buffers; ManagedResource submit_semaphore; vk::DeviceMemory uniform_buffer_memory; vk::DescriptorSetLayout descriptor_set_layout; float rotation; }; vkmark-2017.08+git20220909/src/scenes/vertex_scene.cpp000066400000000000000000000264461441550741700220660ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "vertex_scene.h" #include "mesh.h" #include "model.h" #include "util.h" #include "vulkan_state.h" #include "vulkan_image.h" #include "vkutil/vkutil.h" #include #include #include namespace { struct Uniforms { glm::mat4 modelviewprojection; glm::mat4 normal; glm::vec4 material_diffuse; }; } VertexScene::VertexScene() : Scene{"vertex"} { options_["interleave"] = SceneOption("interleave", "true", "Whether to interleave vertex data"); options_["device-local"] = SceneOption("device-local", "true", "Whether to use a device-local buffer for the vertex data"); } VertexScene::~VertexScene() = default; void VertexScene::setup( VulkanState& vulkan_, std::vector const& vulkan_images) { Scene::setup(vulkan_, vulkan_images); vulkan = &vulkan_; extent = vulkan_images[0].extent; format = vulkan_images[0].format; depth_format = vk::Format::eD32Sfloat; aspect = static_cast(extent.height) / extent.width; mesh = Model{"horse.3ds"}.to_mesh( ModelAttribMap{} .with_position(vk::Format::eR32G32B32Sfloat) .with_normal(vk::Format::eR32G32B32Sfloat)); mesh->set_interleave(options_["interleave"].value == "true"); // Model projection auto const min_bound = mesh->min_attribute_bound(0); auto const max_bound = mesh->max_attribute_bound(0); auto const diameter = glm::length(max_bound - min_bound); auto const aspect = static_cast(extent.width)/static_cast(extent.height); center = (max_bound + min_bound) / 2.0f; radius = diameter / 2.0f; auto const fovy = 2.0f * atanf(radius / (2.0f + radius)); projection = glm::perspective(fovy, aspect, 2.0f, 2.0f + diameter); setup_vertex_buffer(); setup_uniform_buffer(); setup_uniform_descriptor_set(); setup_render_pass(); setup_pipeline(); setup_depth_image(); setup_framebuffers(vulkan_images); setup_command_buffers(); submit_semaphore = vkutil::SemaphoreBuilder{*vulkan}.build(); rotation = 0.0; } void VertexScene::teardown() { vulkan->device().waitIdle(); submit_semaphore = {}; vulkan->device().freeCommandBuffers(vulkan->command_pool(), command_buffers); framebuffers.clear(); image_views.clear(); depth_image_view = {}; depth_image = {}; pipeline = {}; pipeline_layout = {}; render_pass = {}; descriptor_set = {}; uniform_buffer_map = {}; uniform_buffer = {}; vertex_buffer = {}; Scene::teardown(); } VulkanImage VertexScene::draw(VulkanImage const& image) { update_uniforms(); vk::PipelineStageFlags const mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; auto const submit_info = vk::SubmitInfo{} .setCommandBufferCount(1) .setPCommandBuffers(&command_buffers[image.index]) .setWaitSemaphoreCount(image.semaphore ? 1 : 0) .setPWaitSemaphores(&image.semaphore) .setPWaitDstStageMask(&mask) .setSignalSemaphoreCount(1) .setPSignalSemaphores(&submit_semaphore.raw); vulkan->graphics_queue().submit(submit_info, {}); return image.copy_with_semaphore(submit_semaphore); } void VertexScene::update() { auto const t = (Util::get_timestamp_us() - start_time) / 1000000.0f; rotation = 36.0f * t; Scene::update(); } void VertexScene::setup_vertex_buffer() { bool const use_staging_buffer = options_["device-local"].value == "true"; vk::DeviceMemory staging_buffer_memory; vk::BufferUsageFlags staging_usage_flags = vk::BufferUsageFlagBits::eVertexBuffer; if (use_staging_buffer) staging_usage_flags |= vk::BufferUsageFlagBits::eTransferSrc; auto staging_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage(staging_usage_flags) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(staging_buffer_memory) .build(); { auto const staging_buffer_map = vkutil::map_memory( *vulkan, staging_buffer_memory, 0, mesh->vertex_data_size()); mesh->copy_vertex_data_to(staging_buffer_map); } if (use_staging_buffer) { vertex_buffer = vkutil::BufferBuilder{*vulkan} .set_size(mesh->vertex_data_size()) .set_usage( vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst) .set_memory_properties(vk::MemoryPropertyFlagBits::eDeviceLocal) .build(); vkutil::copy_buffer(*vulkan, staging_buffer, vertex_buffer, mesh->vertex_data_size()); } else { vertex_buffer = std::move(staging_buffer); } } void VertexScene::setup_uniform_buffer() { uniform_buffer = vkutil::BufferBuilder{*vulkan} .set_size(sizeof(Uniforms)) .set_usage(vk::BufferUsageFlagBits::eUniformBuffer) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(uniform_buffer_memory) .build(); uniform_buffer_map = vkutil::map_memory( *vulkan, uniform_buffer_memory, 0, sizeof(Uniforms)); } void VertexScene::setup_uniform_descriptor_set() { descriptor_set = vkutil::DescriptorSetBuilder{*vulkan} .set_type(vk::DescriptorType::eUniformBuffer) .set_stage_flags(vk::ShaderStageFlagBits::eVertex) .set_buffer(uniform_buffer, 0, sizeof(Uniforms)) .set_layout_out(descriptor_set_layout) .build(); } void VertexScene::setup_render_pass() { render_pass = vkutil::RenderPassBuilder(*vulkan) .set_color_format(format) .set_depth_format(depth_format) .set_color_load_op(vk::AttachmentLoadOp::eClear) .build(); } void VertexScene::setup_pipeline() { auto const pipeline_layout_create_info = vk::PipelineLayoutCreateInfo{} .setSetLayoutCount(1) .setPSetLayouts(&descriptor_set_layout); pipeline_layout = ManagedResource{ vulkan->device().createPipelineLayout(pipeline_layout_create_info), [this] (auto const& pl) { vulkan->device().destroyPipelineLayout(pl); }}; pipeline = vkutil::PipelineBuilder(*vulkan) .set_extent(extent) .set_layout(pipeline_layout) .set_render_pass(render_pass) .set_vertex_shader(Util::read_data_file("shaders/light-basic.vert.spv")) .set_fragment_shader(Util::read_data_file("shaders/light-basic.frag.spv")) .set_vertex_input(mesh->binding_descriptions(), mesh->attribute_descriptions()) .set_depth_test(true) .build(); } void VertexScene::setup_depth_image() { depth_image = vkutil::ImageBuilder{*vulkan} .set_extent(extent) .set_format(depth_format) .set_tiling(vk::ImageTiling::eOptimal) .set_usage(vk::ImageUsageFlagBits::eDepthStencilAttachment) .set_memory_properties(vk::MemoryPropertyFlagBits::eDeviceLocal) .set_initial_layout(vk::ImageLayout::eUndefined) .build(); vkutil::transition_image_layout( *vulkan, depth_image, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal, vk::ImageAspectFlagBits::eDepth); } void VertexScene::setup_framebuffers(std::vector const& vulkan_images) { depth_image_view = vkutil::ImageViewBuilder{*vulkan} .set_image(depth_image) .set_format(depth_format) .set_aspect_mask(vk::ImageAspectFlagBits::eDepth) .build(); for (auto const& vulkan_image : vulkan_images) { image_views.push_back( vkutil::ImageViewBuilder{*vulkan} .set_image(vulkan_image.image) .set_format(vulkan_image.format) .set_aspect_mask(vk::ImageAspectFlagBits::eColor) .build()); } for (auto const& image_view : image_views) { framebuffers.push_back( vkutil::FramebufferBuilder{*vulkan} .set_render_pass(render_pass) .set_image_views({image_view, depth_image_view}) .set_extent(extent) .build()); } } void VertexScene::setup_command_buffers() { auto const command_buffer_allocate_info = vk::CommandBufferAllocateInfo{} .setCommandPool(vulkan->command_pool()) .setCommandBufferCount(framebuffers.size()) .setLevel(vk::CommandBufferLevel::ePrimary); command_buffers = vulkan->device().allocateCommandBuffers(command_buffer_allocate_info); auto const binding_offsets = mesh->vertex_data_binding_offsets(); for (size_t i = 0; i < command_buffers.size(); ++i) { auto const begin_info = vk::CommandBufferBeginInfo{} .setFlags(vk::CommandBufferUsageFlagBits::eSimultaneousUse); command_buffers[i].begin(begin_info); std::array clear_values{{ vk::ClearColorValue{std::array{{0.0f, 0.0f, 0.0f, 1.0f}}}, vk::ClearDepthStencilValue{1.0f, 0}}}; auto const render_pass_begin_info = vk::RenderPassBeginInfo{} .setRenderPass(render_pass) .setFramebuffer(framebuffers[i]) .setRenderArea({{0,0}, extent}) .setClearValueCount(clear_values.size()) .setPClearValues(clear_values.data()); command_buffers[i].beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); command_buffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); command_buffers[i].bindDescriptorSets( vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set.raw, {}); command_buffers[i].bindVertexBuffers( 0, std::vector{binding_offsets.size(), vertex_buffer.raw}, binding_offsets ); command_buffers[i].draw(mesh->num_vertices(), 1, 0, 0); command_buffers[i].endRenderPass(); command_buffers[i].end(); } } void VertexScene::update_uniforms() { Uniforms ubo; glm::mat4 modelview{1.0}; modelview = glm::translate(modelview, glm::vec3{-center.x, -center.y, -(center.z + 2.0 + radius)}); modelview = glm::rotate(modelview, glm::radians(rotation), {0.0f, 1.0f, 0.0f}); ubo.modelviewprojection = projection * modelview; ubo.normal = glm::inverseTranspose(modelview); ubo.material_diffuse = glm::vec4{0.7f, 0.7f, 0.7f, 1.0}; memcpy(uniform_buffer_map, &ubo, sizeof(ubo)); } vkmark-2017.08+git20220909/src/scenes/vertex_scene.h000066400000000000000000000050471441550741700215250ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "scene.h" #include "managed_resource.h" #include #define GLM_FORCE_DEPTH_ZERO_TO_ONE #include #include class Mesh; class VertexScene : public Scene { public: VertexScene(); ~VertexScene(); void setup(VulkanState&, std::vector const&) override; void teardown() override; VulkanImage draw(VulkanImage const&) override; void update() override; private: void setup_vertex_buffer(); void setup_uniform_buffer(); void setup_uniform_descriptor_set(); void setup_render_pass(); void setup_pipeline(); void setup_depth_image(); void setup_framebuffers(std::vector const&); void setup_command_buffers(); void update_uniforms(); VulkanState* vulkan; vk::Extent2D extent; vk::Format format; vk::Format depth_format; float aspect; glm::mat4 projection; glm::vec3 center; float radius; std::unique_ptr mesh; ManagedResource vertex_buffer; ManagedResource uniform_buffer; ManagedResource uniform_buffer_map; ManagedResource descriptor_set; ManagedResource render_pass; ManagedResource pipeline_layout; ManagedResource pipeline; ManagedResource depth_image; ManagedResource depth_image_view; std::vector> image_views; std::vector> framebuffers; std::vector command_buffers; ManagedResource submit_semaphore; vk::DeviceMemory uniform_buffer_memory; vk::DescriptorSetLayout descriptor_set_layout; float rotation; }; vkmark-2017.08+git20220909/src/stb_image.h000066400000000000000000007517551441550741700175230ustar00rootroot00000000000000/* stb_image - v2.16 - public domain image loader - http://nothings.org/stb_image.h no warranty implied; use at your own risk Do this: #define STB_IMAGE_IMPLEMENTATION before you include this file in *one* C or C++ file to create the implementation. // i.e. it should look like this: #include ... #include ... #include ... #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free QUICK NOTES: Primarily of interest to game developers and other people who can avoid problematic images and only need the trivial interface JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) PNG 1/2/4/8/16-bit-per-channel TGA (not sure what subset, if a subset) BMP non-1bpp, non-RLE PSD (composited view only, no extra channels, 8/16 bit-per-channel) GIF (*comp always reports as 4-channel) HDR (radiance rgbE format) PIC (Softimage PIC) PNM (PPM and PGM binary only) Animated GIF still needs a proper API, but here's one way to do it: http://gist.github.com/urraka/685d9a6340b26b830d49 - decode from memory or through FILE (define STBI_NO_STDIO to remove code) - decode from arbitrary I/O callbacks - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) Full documentation under "DOCUMENTATION" below. LICENSE See end of file for license information. RECENT REVISION HISTORY: 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 RGB-format JPEG; remove white matting in PSD; allocate large structures on the stack; correct channel count for PNG & BMP 2.10 (2016-01-22) avoid warning introduced in 2.09 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED See end of file for full revision history. ============================ Contributors ========================= Image formats Extensions, features Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) github:urraka (animated gif) Junggon Kim (PNM comments) Daniel Gibson (16-bit TGA) socks-the-fox (16-bit PNG) Jeremy Sawicki (handle all ImageNet JPGs) Optimizations & bugfixes Fabian "ryg" Giesen Arseny Kapoulkine John-Mark Allen Bug & warning fixes Marc LeBlanc David Woo Guillaume George Martins Mozeiko Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan Dave Moore Roy Eltham Hayaki Saito Nathan Reed Won Chun Luke Graham Johan Duparc Nick Verigakis the Horde3D community Thomas Ruf Ronny Chevalier Baldur Karlsson Janez Zemva John Bartholomew Michal Cichon github:rlyeh Jonathan Blow Ken Hamada Tero Hanninen github:romigrou Laurent Gomila Cort Stratton Sergio Gonzalez github:svdijk Aruelien Pocheville Thibault Reuille Cass Everitt github:snagar Ryamond Barbiero Paul Du Bois Engin Manap github:Zelex Michaelangel007@github Philipp Wiesemann Dale Weiler github:grim210 Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:sammyhw Blazej Dariusz Roszkowski Gregory Mullen github:phprus Christian Floisand Kevin Schmidt github:poppolopoppo */ #ifndef STBI_INCLUDE_STB_IMAGE_H #define STBI_INCLUDE_STB_IMAGE_H // DOCUMENTATION // // Limitations: // - no 16-bit-per-channel PNG // - no 12-bit-per-channel JPEG // - no JPEGs with arithmetic coding // - no 1-bit BMP // - GIF always returns *comp=4 // // Basic usage (see HDR discussion below for HDR usage): // int x,y,n; // unsigned char *data = stbi_load(filename, &x, &y, &n, 0); // // ... process data if not NULL ... // // ... x = width, y = height, n = # 8-bit components per pixel ... // // ... replace '0' with '1'..'4' to force that many components per pixel // // ... but 'n' will always be the number that it would have been if you said 0 // stbi_image_free(data) // // Standard parameters: // int *x -- outputs image width in pixels // int *y -- outputs image height in pixels // int *channels_in_file -- outputs # of image components in image file // int desired_channels -- if non-zero, # of image components requested in result // // The return value from an image loader is an 'unsigned char *' which points // to the pixel data, or NULL on an allocation failure or if the image is // corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, // with each pixel consisting of N interleaved 8-bit components; the first // pixel pointed to is top-left-most in the image. There is no padding between // image scanlines or between pixels, regardless of format. The number of // components N is 'desired_channels' if desired_channels is non-zero, or // *channels_in_file otherwise. If desired_channels is non-zero, // *channels_in_file has the number of components that _would_ have been // output otherwise. E.g. if you set desired_channels to 4, you will always // get RGBA output, but you can check *channels_in_file to see if it's trivially // opaque because e.g. there were only 3 channels in the source image. // // An output image with N components has the following components interleaved // in this order in each pixel: // // N=#comp components // 1 grey // 2 grey, alpha // 3 red, green, blue // 4 red, green, blue, alpha // // If image loading fails for any reason, the return value will be NULL, // and *x, *y, *channels_in_file will be unchanged. The function // stbi_failure_reason() can be queried for an extremely brief, end-user // unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS // to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly // more user-friendly ones. // // Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. // // =========================================================================== // // Philosophy // // stb libraries are designed with the following priorities: // // 1. easy to use // 2. easy to maintain // 3. good performance // // Sometimes I let "good performance" creep up in priority over "easy to maintain", // and for best performance I may provide less-easy-to-use APIs that give higher // performance, in addition to the easy to use ones. Nevertheless, it's important // to keep in mind that from the standpoint of you, a client of this library, // all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. // // Some secondary priorities arise directly from the first two, some of which // make more explicit reasons why performance can't be emphasized. // // - Portable ("ease of use") // - Small source code footprint ("easy to maintain") // - No dependencies ("ease of use") // // =========================================================================== // // I/O callbacks // // I/O callbacks allow you to read from arbitrary sources, like packaged // files or some other source. Data read from callbacks are processed // through a small internal buffer (currently 128 bytes) to try to reduce // overhead. // // The three functions you must define are "read" (reads some bytes of data), // "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). // // =========================================================================== // // SIMD support // // The JPEG decoder will try to automatically use SIMD kernels on x86 when // supported by the compiler. For ARM Neon support, you must explicitly // request it. // // (The old do-it-yourself SIMD API is no longer supported in the current // code.) // // On x86, SSE2 will automatically be used when available based on a run-time // test; if not, the generic C versions are used as a fall-back. On ARM targets, // the typical path is to have separate builds for NEON and non-NEON devices // (at least this is true for iOS and Android). Therefore, the NEON support is // toggled by a build flag: define STBI_NEON to get NEON loops. // // If for some reason you do not want to use any of SIMD code, or if // you have issues compiling it, you can disable it entirely by // defining STBI_NO_SIMD. // // =========================================================================== // // HDR image support (disable by defining STBI_NO_HDR) // // stb_image now supports loading HDR images in general, and currently // the Radiance .HDR file format, although the support is provided // generically. You can still load any file through the existing interface; // if you attempt to load an HDR file, it will be automatically remapped to // LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; // both of these constants can be reconfigured through this interface: // // stbi_hdr_to_ldr_gamma(2.2f); // stbi_hdr_to_ldr_scale(1.0f); // // (note, do not use _inverse_ constants; stbi_image will invert them // appropriately). // // Additionally, there is a new, parallel interface for loading files as // (linear) floats to preserve the full dynamic range: // // float *data = stbi_loadf(filename, &x, &y, &n, 0); // // If you load LDR images through this interface, those images will // be promoted to floating point values, run through the inverse of // constants corresponding to the above: // // stbi_ldr_to_hdr_scale(1.0f); // stbi_ldr_to_hdr_gamma(2.2f); // // Finally, given a filename (or an open file or memory block--see header // file for details) containing image data, you can query for the "most // appropriate" interface to use (that is, whether the image is HDR or // not), using: // // stbi_is_hdr(char *filename); // // =========================================================================== // // iPhone PNG support: // // By default we convert iphone-formatted PNGs back to RGB, even though // they are internally encoded differently. You can disable this conversion // by by calling stbi_convert_iphone_png_to_rgb(0), in which case // you will always just get the native iphone "format" through (which // is BGR stored in RGB). // // Call stbi_set_unpremultiply_on_load(1) as well to force a divide per // pixel to remove any premultiplied alpha *only* if the image file explicitly // says there's premultiplied data (currently only happens in iPhone images, // and only if iPhone convert-to-rgb processing is on). // // =========================================================================== // // ADDITIONAL CONFIGURATION // // - You can suppress implementation of any of the decoders to reduce // your code footprint by #defining one or more of the following // symbols before creating the implementation. // // STBI_NO_JPEG // STBI_NO_PNG // STBI_NO_BMP // STBI_NO_PSD // STBI_NO_TGA // STBI_NO_GIF // STBI_NO_HDR // STBI_NO_PIC // STBI_NO_PNM (.ppm and .pgm) // // - You can request *only* certain decoders and suppress all other ones // (this will be more forward-compatible, as addition of new decoders // doesn't require you to disable them explicitly): // // STBI_ONLY_JPEG // STBI_ONLY_PNG // STBI_ONLY_BMP // STBI_ONLY_PSD // STBI_ONLY_TGA // STBI_ONLY_GIF // STBI_ONLY_HDR // STBI_ONLY_PIC // STBI_ONLY_PNM (.ppm and .pgm) // // - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still // want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB // #ifndef STBI_NO_STDIO #include #endif // STBI_NO_STDIO #define STBI_VERSION 1 enum { STBI_default = 0, // only used for desired_channels STBI_grey = 1, STBI_grey_alpha = 2, STBI_rgb = 3, STBI_rgb_alpha = 4 }; typedef unsigned char stbi_uc; typedef unsigned short stbi_us; #ifdef __cplusplus extern "C" { #endif #ifdef STB_IMAGE_STATIC #define STBIDEF static #else #define STBIDEF extern #endif ////////////////////////////////////////////////////////////////////////////// // // PRIMARY API - works on images of any type // // // load image by filename, open file, or memory buffer // typedef struct { int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative int (*eof) (void *user); // returns nonzero if we are at end of file/data } stbi_io_callbacks; //////////////////////////////////// // // 8-bits-per-channel interface // STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); #ifndef STBI_NO_STDIO STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); // for stbi_load_from_file, file pointer is left pointing immediately after image #endif //////////////////////////////////// // // 16-bits-per-channel interface // STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); #ifndef STBI_NO_STDIO STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); #endif //////////////////////////////////// // // float-per-channel interface // #ifndef STBI_NO_LINEAR STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); #ifndef STBI_NO_STDIO STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); #endif #endif #ifndef STBI_NO_HDR STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); STBIDEF void stbi_hdr_to_ldr_scale(float scale); #endif // STBI_NO_HDR #ifndef STBI_NO_LINEAR STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); STBIDEF void stbi_ldr_to_hdr_scale(float scale); #endif // STBI_NO_LINEAR // stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); #ifndef STBI_NO_STDIO STBIDEF int stbi_is_hdr (char const *filename); STBIDEF int stbi_is_hdr_from_file(FILE *f); #endif // STBI_NO_STDIO // get a VERY brief reason for failure // NOT THREADSAFE STBIDEF const char *stbi_failure_reason (void); // free the loaded image -- this is just free() STBIDEF void stbi_image_free (void *retval_from_stbi_load); // get image dimensions & components without fully decoding STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); #ifndef STBI_NO_STDIO STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); #endif // for image formats that explicitly notate that they have premultiplied alpha, // we just return the colors as stored in the file. set this flag to force // unpremultiplication. results are undefined if the unpremultiply overflow. STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); // indicate whether we should process iphone images back to canonical format, // or just pass them through "as-is" STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); // flip the image vertically, so the first pixel in the output array is the bottom left STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); // ZLIB client - used by PNG, available for other purposes STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); #ifdef __cplusplus } #endif // // //// end header file ///////////////////////////////////////////////////// #endif // STBI_INCLUDE_STB_IMAGE_H #ifdef STB_IMAGE_IMPLEMENTATION #if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ || defined(STBI_ONLY_ZLIB) #ifndef STBI_ONLY_JPEG #define STBI_NO_JPEG #endif #ifndef STBI_ONLY_PNG #define STBI_NO_PNG #endif #ifndef STBI_ONLY_BMP #define STBI_NO_BMP #endif #ifndef STBI_ONLY_PSD #define STBI_NO_PSD #endif #ifndef STBI_ONLY_TGA #define STBI_NO_TGA #endif #ifndef STBI_ONLY_GIF #define STBI_NO_GIF #endif #ifndef STBI_ONLY_HDR #define STBI_NO_HDR #endif #ifndef STBI_ONLY_PIC #define STBI_NO_PIC #endif #ifndef STBI_ONLY_PNM #define STBI_NO_PNM #endif #endif #if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) #define STBI_NO_ZLIB #endif #include #include // ptrdiff_t on osx #include #include #include #if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) #include // ldexp #endif #ifndef STBI_NO_STDIO #include #endif #ifndef STBI_ASSERT #include #define STBI_ASSERT(x) assert(x) #endif #ifndef _MSC_VER #ifdef __cplusplus #define stbi_inline inline #else #define stbi_inline #endif #else #define stbi_inline __forceinline #endif #ifdef _MSC_VER typedef unsigned short stbi__uint16; typedef signed short stbi__int16; typedef unsigned int stbi__uint32; typedef signed int stbi__int32; #else #include typedef uint16_t stbi__uint16; typedef int16_t stbi__int16; typedef uint32_t stbi__uint32; typedef int32_t stbi__int32; #endif // should produce compiler error if size is wrong typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; #ifdef _MSC_VER #define STBI_NOTUSED(v) (void)(v) #else #define STBI_NOTUSED(v) (void)sizeof(v) #endif #ifdef _MSC_VER #define STBI_HAS_LROTL #endif #ifdef STBI_HAS_LROTL #define stbi_lrot(x,y) _lrotl(x,y) #else #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) #endif #if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) // ok #elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) // ok #else #error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." #endif #ifndef STBI_MALLOC #define STBI_MALLOC(sz) malloc(sz) #define STBI_REALLOC(p,newsz) realloc(p,newsz) #define STBI_FREE(p) free(p) #endif #ifndef STBI_REALLOC_SIZED #define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) #endif // x86/x64 detection #if defined(__x86_64__) || defined(_M_X64) #define STBI__X64_TARGET #elif defined(__i386) || defined(_M_IX86) #define STBI__X86_TARGET #endif #if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) // gcc doesn't support sse2 intrinsics unless you compile with -msse2, // which in turn means it gets to use SSE2 everywhere. This is unfortunate, // but previous attempts to provide the SSE2 functions with runtime // detection caused numerous issues. The way architecture extensions are // exposed in GCC/Clang is, sadly, not really suited for one-file libs. // New behavior: if compiled with -msse2, we use SSE2 without any // detection; if not, we don't use it at all. #define STBI_NO_SIMD #endif #if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) // Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET // // 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the // Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. // As a result, enabling SSE2 on 32-bit MinGW is dangerous when not // simultaneously enabling "-mstackrealign". // // See https://github.com/nothings/stb/issues/81 for more information. // // So default to no SSE2 on 32-bit MinGW. If you've read this far and added // -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. #define STBI_NO_SIMD #endif #if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) #define STBI_SSE2 #include #ifdef _MSC_VER #if _MSC_VER >= 1400 // not VC6 #include // __cpuid static int stbi__cpuid3(void) { int info[4]; __cpuid(info,1); return info[3]; } #else static int stbi__cpuid3(void) { int res; __asm { mov eax,1 cpuid mov res,edx } return res; } #endif #define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name static int stbi__sse2_available(void) { int info3 = stbi__cpuid3(); return ((info3 >> 26) & 1) != 0; } #else // assume GCC-style if not VC++ #define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) static int stbi__sse2_available(void) { // If we're even attempting to compile this on GCC/Clang, that means // -msse2 is on, which means the compiler is allowed to use SSE2 // instructions at will, and so are we. return 1; } #endif #endif // ARM NEON #if defined(STBI_NO_SIMD) && defined(STBI_NEON) #undef STBI_NEON #endif #ifdef STBI_NEON #include // assume GCC or Clang on ARM targets #define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) #endif #ifndef STBI_SIMD_ALIGN #define STBI_SIMD_ALIGN(type, name) type name #endif /////////////////////////////////////////////// // // stbi__context struct and start_xxx functions // stbi__context structure is our basic context used by all images, so it // contains all the IO context, plus some basic image information typedef struct { stbi__uint32 img_x, img_y; int img_n, img_out_n; stbi_io_callbacks io; void *io_user_data; int read_from_callbacks; int buflen; stbi_uc buffer_start[128]; stbi_uc *img_buffer, *img_buffer_end; stbi_uc *img_buffer_original, *img_buffer_original_end; } stbi__context; static void stbi__refill_buffer(stbi__context *s); // initialize a memory-decode context static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) { s->io.read = NULL; s->read_from_callbacks = 0; s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; } // initialize a callback-based context static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) { s->io = *c; s->io_user_data = user; s->buflen = sizeof(s->buffer_start); s->read_from_callbacks = 1; s->img_buffer_original = s->buffer_start; stbi__refill_buffer(s); s->img_buffer_original_end = s->img_buffer_end; } #ifndef STBI_NO_STDIO static int stbi__stdio_read(void *user, char *data, int size) { return (int) fread(data,1,size,(FILE*) user); } static void stbi__stdio_skip(void *user, int n) { fseek((FILE*) user, n, SEEK_CUR); } static int stbi__stdio_eof(void *user) { return feof((FILE*) user); } static stbi_io_callbacks stbi__stdio_callbacks = { stbi__stdio_read, stbi__stdio_skip, stbi__stdio_eof, }; static void stbi__start_file(stbi__context *s, FILE *f) { stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); } //static void stop_file(stbi__context *s) { } #endif // !STBI_NO_STDIO static void stbi__rewind(stbi__context *s) { // conceptually rewind SHOULD rewind to the beginning of the stream, // but we just rewind to the beginning of the initial buffer, because // we only use it after doing 'test', which only ever looks at at most 92 bytes s->img_buffer = s->img_buffer_original; s->img_buffer_end = s->img_buffer_original_end; } enum { STBI_ORDER_RGB, STBI_ORDER_BGR }; typedef struct { int bits_per_channel; int num_channels; int channel_order; } stbi__result_info; #ifndef STBI_NO_JPEG static int stbi__jpeg_test(stbi__context *s); static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PNG static int stbi__png_test(stbi__context *s); static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_BMP static int stbi__bmp_test(stbi__context *s); static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_TGA static int stbi__tga_test(stbi__context *s); static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PSD static int stbi__psd_test(stbi__context *s); static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_HDR static int stbi__hdr_test(stbi__context *s); static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PIC static int stbi__pic_test(stbi__context *s); static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_GIF static int stbi__gif_test(stbi__context *s); static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PNM static int stbi__pnm_test(stbi__context *s); static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); #endif // this is not threadsafe static const char *stbi__g_failure_reason; STBIDEF const char *stbi_failure_reason(void) { return stbi__g_failure_reason; } static int stbi__err(const char *str) { stbi__g_failure_reason = str; return 0; } static void *stbi__malloc(size_t size) { return STBI_MALLOC(size); } // stb_image uses ints pervasively, including for offset calculations. // therefore the largest decoded image size we can support with the // current code, even on 64-bit targets, is INT_MAX. this is not a // significant limitation for the intended use case. // // we do, however, need to make sure our size calculations don't // overflow. hence a few helper functions for size calculations that // multiply integers together, making sure that they're non-negative // and no overflow occurs. // return 1 if the sum is valid, 0 on overflow. // negative terms are considered invalid. static int stbi__addsizes_valid(int a, int b) { if (b < 0) return 0; // now 0 <= b <= INT_MAX, hence also // 0 <= INT_MAX - b <= INTMAX. // And "a + b <= INT_MAX" (which might overflow) is the // same as a <= INT_MAX - b (no overflow) return a <= INT_MAX - b; } // returns 1 if the product is valid, 0 on overflow. // negative factors are considered invalid. static int stbi__mul2sizes_valid(int a, int b) { if (a < 0 || b < 0) return 0; if (b == 0) return 1; // mul-by-0 is always safe // portable way to check for no overflows in a*b return a <= INT_MAX/b; } // returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow static int stbi__mad2sizes_valid(int a, int b, int add) { return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); } // returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow static int stbi__mad3sizes_valid(int a, int b, int c, int add) { return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && stbi__addsizes_valid(a*b*c, add); } // returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) { return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); } // mallocs with size overflow checking static void *stbi__malloc_mad2(int a, int b, int add) { if (!stbi__mad2sizes_valid(a, b, add)) return NULL; return stbi__malloc(a*b + add); } static void *stbi__malloc_mad3(int a, int b, int c, int add) { if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; return stbi__malloc(a*b*c + add); } static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) { if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; return stbi__malloc(a*b*c*d + add); } // stbi__err - error // stbi__errpf - error returning pointer to float // stbi__errpuc - error returning pointer to unsigned char #ifdef STBI_NO_FAILURE_STRINGS #define stbi__err(x,y) 0 #elif defined(STBI_FAILURE_USERMSG) #define stbi__err(x,y) stbi__err(y) #else #define stbi__err(x,y) stbi__err(x) #endif #define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) #define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) STBIDEF void stbi_image_free(void *retval_from_stbi_load) { STBI_FREE(retval_from_stbi_load); } #ifndef STBI_NO_LINEAR static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); #endif #ifndef STBI_NO_HDR static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); #endif static int stbi__vertically_flip_on_load = 0; STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) { stbi__vertically_flip_on_load = flag_true_if_should_flip; } static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) { memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order ri->num_channels = 0; #ifndef STBI_NO_JPEG if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_PNG if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_BMP if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_GIF if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_PSD if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); #endif #ifndef STBI_NO_PIC if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_PNM if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_HDR if (stbi__hdr_test(s)) { float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); } #endif #ifndef STBI_NO_TGA // test tga last because it's a crappy test! if (stbi__tga_test(s)) return stbi__tga_load(s,x,y,comp,req_comp, ri); #endif return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); } static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) { int i; int img_len = w * h * channels; stbi_uc *reduced; reduced = (stbi_uc *) stbi__malloc(img_len); if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); for (i = 0; i < img_len; ++i) reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling STBI_FREE(orig); return reduced; } static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) { int i; int img_len = w * h * channels; stbi__uint16 *enlarged; enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); for (i = 0; i < img_len; ++i) enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff STBI_FREE(orig); return enlarged; } static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) { int row; size_t bytes_per_row = (size_t)w * bytes_per_pixel; stbi_uc temp[2048]; stbi_uc *bytes = (stbi_uc *)image; for (row = 0; row < (h>>1); row++) { stbi_uc *row0 = bytes + row*bytes_per_row; stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; // swap row0 with row1 size_t bytes_left = bytes_per_row; while (bytes_left) { size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); memcpy(temp, row0, bytes_copy); memcpy(row0, row1, bytes_copy); memcpy(row1, temp, bytes_copy); row0 += bytes_copy; row1 += bytes_copy; bytes_left -= bytes_copy; } } } static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi__result_info ri; void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); if (result == NULL) return NULL; if (ri.bits_per_channel != 8) { STBI_ASSERT(ri.bits_per_channel == 16); result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); ri.bits_per_channel = 8; } // @TODO: move stbi__convert_format to here if (stbi__vertically_flip_on_load) { int channels = req_comp ? req_comp : *comp; stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); } return (unsigned char *) result; } static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi__result_info ri; void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); if (result == NULL) return NULL; if (ri.bits_per_channel != 16) { STBI_ASSERT(ri.bits_per_channel == 8); result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); ri.bits_per_channel = 16; } // @TODO: move stbi__convert_format16 to here // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision if (stbi__vertically_flip_on_load) { int channels = req_comp ? req_comp : *comp; stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); } return (stbi__uint16 *) result; } #ifndef STBI_NO_HDR static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) { if (stbi__vertically_flip_on_load && result != NULL) { int channels = req_comp ? req_comp : *comp; stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); } } #endif #ifndef STBI_NO_STDIO static FILE *stbi__fopen(char const *filename, char const *mode) { FILE *f; #if defined(_MSC_VER) && _MSC_VER >= 1400 if (0 != fopen_s(&f, filename, mode)) f=0; #else f = fopen(filename, mode); #endif return f; } STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) { FILE *f = stbi__fopen(filename, "rb"); unsigned char *result; if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); result = stbi_load_from_file(f,x,y,comp,req_comp); fclose(f); return result; } STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) { unsigned char *result; stbi__context s; stbi__start_file(&s,f); result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); if (result) { // need to 'unget' all the characters in the IO buffer fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); } return result; } STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) { stbi__uint16 *result; stbi__context s; stbi__start_file(&s,f); result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); if (result) { // need to 'unget' all the characters in the IO buffer fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); } return result; } STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) { FILE *f = stbi__fopen(filename, "rb"); stbi__uint16 *result; if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); result = stbi_load_from_file_16(f,x,y,comp,req_comp); fclose(f); return result; } #endif //!STBI_NO_STDIO STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) { stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); } STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); } STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); } STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); } #ifndef STBI_NO_LINEAR static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) { unsigned char *data; #ifndef STBI_NO_HDR if (stbi__hdr_test(s)) { stbi__result_info ri; float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); if (hdr_data) stbi__float_postprocess(hdr_data,x,y,comp,req_comp); return hdr_data; } #endif data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); if (data) return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); } STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__loadf_main(&s,x,y,comp,req_comp); } STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); return stbi__loadf_main(&s,x,y,comp,req_comp); } #ifndef STBI_NO_STDIO STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) { float *result; FILE *f = stbi__fopen(filename, "rb"); if (!f) return stbi__errpf("can't fopen", "Unable to open file"); result = stbi_loadf_from_file(f,x,y,comp,req_comp); fclose(f); return result; } STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_file(&s,f); return stbi__loadf_main(&s,x,y,comp,req_comp); } #endif // !STBI_NO_STDIO #endif // !STBI_NO_LINEAR // these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is // defined, for API simplicity; if STBI_NO_LINEAR is defined, it always // reports false! STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) { #ifndef STBI_NO_HDR stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__hdr_test(&s); #else STBI_NOTUSED(buffer); STBI_NOTUSED(len); return 0; #endif } #ifndef STBI_NO_STDIO STBIDEF int stbi_is_hdr (char const *filename) { FILE *f = stbi__fopen(filename, "rb"); int result=0; if (f) { result = stbi_is_hdr_from_file(f); fclose(f); } return result; } STBIDEF int stbi_is_hdr_from_file(FILE *f) { #ifndef STBI_NO_HDR stbi__context s; stbi__start_file(&s,f); return stbi__hdr_test(&s); #else STBI_NOTUSED(f); return 0; #endif } #endif // !STBI_NO_STDIO STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) { #ifndef STBI_NO_HDR stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); return stbi__hdr_test(&s); #else STBI_NOTUSED(clbk); STBI_NOTUSED(user); return 0; #endif } #ifndef STBI_NO_LINEAR static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } #endif static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } ////////////////////////////////////////////////////////////////////////////// // // Common code used by all image loaders // enum { STBI__SCAN_load=0, STBI__SCAN_type, STBI__SCAN_header }; static void stbi__refill_buffer(stbi__context *s) { int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); if (n == 0) { // at end of file, treat same as if from memory, but need to handle case // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file s->read_from_callbacks = 0; s->img_buffer = s->buffer_start; s->img_buffer_end = s->buffer_start+1; *s->img_buffer = 0; } else { s->img_buffer = s->buffer_start; s->img_buffer_end = s->buffer_start + n; } } stbi_inline static stbi_uc stbi__get8(stbi__context *s) { if (s->img_buffer < s->img_buffer_end) return *s->img_buffer++; if (s->read_from_callbacks) { stbi__refill_buffer(s); return *s->img_buffer++; } return 0; } stbi_inline static int stbi__at_eof(stbi__context *s) { if (s->io.read) { if (!(s->io.eof)(s->io_user_data)) return 0; // if feof() is true, check if buffer = end // special case: we've only got the special 0 character at the end if (s->read_from_callbacks == 0) return 1; } return s->img_buffer >= s->img_buffer_end; } static void stbi__skip(stbi__context *s, int n) { if (n < 0) { s->img_buffer = s->img_buffer_end; return; } if (s->io.read) { int blen = (int) (s->img_buffer_end - s->img_buffer); if (blen < n) { s->img_buffer = s->img_buffer_end; (s->io.skip)(s->io_user_data, n - blen); return; } } s->img_buffer += n; } static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) { if (s->io.read) { int blen = (int) (s->img_buffer_end - s->img_buffer); if (blen < n) { int res, count; memcpy(buffer, s->img_buffer, blen); count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); res = (count == (n-blen)); s->img_buffer = s->img_buffer_end; return res; } } if (s->img_buffer+n <= s->img_buffer_end) { memcpy(buffer, s->img_buffer, n); s->img_buffer += n; return 1; } else return 0; } static int stbi__get16be(stbi__context *s) { int z = stbi__get8(s); return (z << 8) + stbi__get8(s); } static stbi__uint32 stbi__get32be(stbi__context *s) { stbi__uint32 z = stbi__get16be(s); return (z << 16) + stbi__get16be(s); } #if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) // nothing #else static int stbi__get16le(stbi__context *s) { int z = stbi__get8(s); return z + (stbi__get8(s) << 8); } #endif #ifndef STBI_NO_BMP static stbi__uint32 stbi__get32le(stbi__context *s) { stbi__uint32 z = stbi__get16le(s); return z + (stbi__get16le(s) << 16); } #endif #define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings ////////////////////////////////////////////////////////////////////////////// // // generic converter from built-in img_n to req_comp // individual types do this automatically as much as possible (e.g. jpeg // does all cases internally since it needs to colorspace convert anyway, // and it never has alpha, so very few cases ). png can automatically // interleave an alpha=255 channel, but falls back to this for other cases // // assume data buffer is malloced, so malloc a new one and free that one // only failure mode is malloc failing static stbi_uc stbi__compute_y(int r, int g, int b) { return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); } static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) { int i,j; unsigned char *good; if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); if (good == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } for (j=0; j < (int) y; ++j) { unsigned char *src = data + j * x * img_n ; unsigned char *dest = good + j * x * req_comp; #define STBI__COMBO(a,b) ((a)*8+(b)) #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) // convert source image with img_n components to one with req_comp components; // avoid switch per pixel, so use switch per scanline and massive macros switch (STBI__COMBO(img_n, req_comp)) { STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; STBI__CASE(2,1) { dest[0]=src[0]; } break; STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; default: STBI_ASSERT(0); } #undef STBI__CASE } STBI_FREE(data); return good; } static stbi__uint16 stbi__compute_y_16(int r, int g, int b) { return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); } static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) { int i,j; stbi__uint16 *good; if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); if (good == NULL) { STBI_FREE(data); return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); } for (j=0; j < (int) y; ++j) { stbi__uint16 *src = data + j * x * img_n ; stbi__uint16 *dest = good + j * x * req_comp; #define STBI__COMBO(a,b) ((a)*8+(b)) #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) // convert source image with img_n components to one with req_comp components; // avoid switch per pixel, so use switch per scanline and massive macros switch (STBI__COMBO(img_n, req_comp)) { STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; STBI__CASE(2,1) { dest[0]=src[0]; } break; STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; default: STBI_ASSERT(0); } #undef STBI__CASE } STBI_FREE(data); return good; } #ifndef STBI_NO_LINEAR static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) { int i,k,n; float *output; if (!data) return NULL; output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } // compute number of non-alpha components if (comp & 1) n = comp; else n = comp-1; for (i=0; i < x*y; ++i) { for (k=0; k < n; ++k) { output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); } if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; } STBI_FREE(data); return output; } #endif #ifndef STBI_NO_HDR #define stbi__float2int(x) ((int) (x)) static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) { int i,k,n; stbi_uc *output; if (!data) return NULL; output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } // compute number of non-alpha components if (comp & 1) n = comp; else n = comp-1; for (i=0; i < x*y; ++i) { for (k=0; k < n; ++k) { float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; if (z < 0) z = 0; if (z > 255) z = 255; output[i*comp + k] = (stbi_uc) stbi__float2int(z); } if (k < comp) { float z = data[i*comp+k] * 255 + 0.5f; if (z < 0) z = 0; if (z > 255) z = 255; output[i*comp + k] = (stbi_uc) stbi__float2int(z); } } STBI_FREE(data); return output; } #endif ////////////////////////////////////////////////////////////////////////////// // // "baseline" JPEG/JFIF decoder // // simple implementation // - doesn't support delayed output of y-dimension // - simple interface (only one output format: 8-bit interleaved RGB) // - doesn't try to recover corrupt jpegs // - doesn't allow partial loading, loading multiple at once // - still fast on x86 (copying globals into locals doesn't help x86) // - allocates lots of intermediate memory (full size of all components) // - non-interleaved case requires this anyway // - allows good upsampling (see next) // high-quality // - upsampled channels are bilinearly interpolated, even across blocks // - quality integer IDCT derived from IJG's 'slow' // performance // - fast huffman; reasonable integer IDCT // - some SIMD kernels for common paths on targets with SSE2/NEON // - uses a lot of intermediate memory, could cache poorly #ifndef STBI_NO_JPEG // huffman decoding acceleration #define FAST_BITS 9 // larger handles more cases; smaller stomps less cache typedef struct { stbi_uc fast[1 << FAST_BITS]; // weirdly, repacking this into AoS is a 10% speed loss, instead of a win stbi__uint16 code[256]; stbi_uc values[256]; stbi_uc size[257]; unsigned int maxcode[18]; int delta[17]; // old 'firstsymbol' - old 'firstcode' } stbi__huffman; typedef struct { stbi__context *s; stbi__huffman huff_dc[4]; stbi__huffman huff_ac[4]; stbi__uint16 dequant[4][64]; stbi__int16 fast_ac[4][1 << FAST_BITS]; // sizes for components, interleaved MCUs int img_h_max, img_v_max; int img_mcu_x, img_mcu_y; int img_mcu_w, img_mcu_h; // definition of jpeg image component struct { int id; int h,v; int tq; int hd,ha; int dc_pred; int x,y,w2,h2; stbi_uc *data; void *raw_data, *raw_coeff; stbi_uc *linebuf; short *coeff; // progressive only int coeff_w, coeff_h; // number of 8x8 coefficient blocks } img_comp[4]; stbi__uint32 code_buffer; // jpeg entropy-coded buffer int code_bits; // number of valid bits unsigned char marker; // marker seen while filling entropy buffer int nomore; // flag if we saw a marker so must stop int progressive; int spec_start; int spec_end; int succ_high; int succ_low; int eob_run; int jfif; int app14_color_transform; // Adobe APP14 tag int rgb; int scan_n, order[4]; int restart_interval, todo; // kernels void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); } stbi__jpeg; static int stbi__build_huffman(stbi__huffman *h, int *count) { int i,j,k=0,code; // build size list for each symbol (from JPEG spec) for (i=0; i < 16; ++i) for (j=0; j < count[i]; ++j) h->size[k++] = (stbi_uc) (i+1); h->size[k] = 0; // compute actual symbols (from jpeg spec) code = 0; k = 0; for(j=1; j <= 16; ++j) { // compute delta to add to code to compute symbol id h->delta[j] = k - code; if (h->size[k] == j) { while (h->size[k] == j) h->code[k++] = (stbi__uint16) (code++); if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); } // compute largest code + 1 for this size, preshifted as needed later h->maxcode[j] = code << (16-j); code <<= 1; } h->maxcode[j] = 0xffffffff; // build non-spec acceleration table; 255 is flag for not-accelerated memset(h->fast, 255, 1 << FAST_BITS); for (i=0; i < k; ++i) { int s = h->size[i]; if (s <= FAST_BITS) { int c = h->code[i] << (FAST_BITS-s); int m = 1 << (FAST_BITS-s); for (j=0; j < m; ++j) { h->fast[c+j] = (stbi_uc) i; } } } return 1; } // build a table that decodes both magnitude and value of small ACs in // one go. static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) { int i; for (i=0; i < (1 << FAST_BITS); ++i) { stbi_uc fast = h->fast[i]; fast_ac[i] = 0; if (fast < 255) { int rs = h->values[fast]; int run = (rs >> 4) & 15; int magbits = rs & 15; int len = h->size[fast]; if (magbits && len + magbits <= FAST_BITS) { // magnitude code followed by receive_extend code int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); int m = 1 << (magbits - 1); if (k < m) k += (~0U << magbits) + 1; // if the result is small enough, we can fit it in fast_ac table if (k >= -128 && k <= 127) fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); } } } } static void stbi__grow_buffer_unsafe(stbi__jpeg *j) { do { int b = j->nomore ? 0 : stbi__get8(j->s); if (b == 0xff) { int c = stbi__get8(j->s); while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes if (c != 0) { j->marker = (unsigned char) c; j->nomore = 1; return; } } j->code_buffer |= b << (24 - j->code_bits); j->code_bits += 8; } while (j->code_bits <= 24); } // (1 << n) - 1 static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; // decode a jpeg huffman value from the bitstream stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) { unsigned int temp; int c,k; if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); // look at the top FAST_BITS and determine what symbol ID it is, // if the code is <= FAST_BITS c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); k = h->fast[c]; if (k < 255) { int s = h->size[k]; if (s > j->code_bits) return -1; j->code_buffer <<= s; j->code_bits -= s; return h->values[k]; } // naive test is to shift the code_buffer down so k bits are // valid, then test against maxcode. To speed this up, we've // preshifted maxcode left so that it has (16-k) 0s at the // end; in other words, regardless of the number of bits, it // wants to be compared against something shifted to have 16; // that way we don't need to shift inside the loop. temp = j->code_buffer >> 16; for (k=FAST_BITS+1 ; ; ++k) if (temp < h->maxcode[k]) break; if (k == 17) { // error! code not found j->code_bits -= 16; return -1; } if (k > j->code_bits) return -1; // convert the huffman code to the symbol id c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); // convert the id to a symbol j->code_bits -= k; j->code_buffer <<= k; return h->values[c]; } // bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB k = stbi_lrot(j->code_buffer, n); STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; j->code_bits -= n; return k + (stbi__jbias[n] & ~sgn); } // get some unsigned bits stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) { unsigned int k; if (j->code_bits < n) stbi__grow_buffer_unsafe(j); k = stbi_lrot(j->code_buffer, n); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; j->code_bits -= n; return k; } stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) { unsigned int k; if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); k = j->code_buffer; j->code_buffer <<= 1; --j->code_bits; return k & 0x80000000; } // given a value that's at position X in the zigzag stream, // where does it appear in the 8x8 matrix coded as row-major? static stbi_uc stbi__jpeg_dezigzag[64+15] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, // let corrupt input sample past end 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }; // decode one 64-entry block-- static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) { int diff,dc,k; int t; if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); t = stbi__jpeg_huff_decode(j, hdc); if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); // 0 all the ac values now so we can do it 32-bits at a time memset(data,0,64*sizeof(data[0])); diff = t ? stbi__extend_receive(j, t) : 0; dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; data[0] = (short) (dc * dequant[0]); // decode AC components, see JPEG spec k = 1; do { unsigned int zig; int c,r,s; if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); r = fac[c]; if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length j->code_buffer <<= s; j->code_bits -= s; // decode into unzigzag'd location zig = stbi__jpeg_dezigzag[k++]; data[zig] = (short) ((r >> 8) * dequant[zig]); } else { int rs = stbi__jpeg_huff_decode(j, hac); if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); s = rs & 15; r = rs >> 4; if (s == 0) { if (rs != 0xf0) break; // end block k += 16; } else { k += r; // decode into unzigzag'd location zig = stbi__jpeg_dezigzag[k++]; data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); } } } while (k < 64); return 1; } static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) { int diff,dc; int t; if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); if (j->succ_high == 0) { // first scan for DC coefficient, must be first memset(data,0,64*sizeof(data[0])); // 0 all the ac values now t = stbi__jpeg_huff_decode(j, hdc); diff = t ? stbi__extend_receive(j, t) : 0; dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; data[0] = (short) (dc << j->succ_low); } else { // refinement scan for DC coefficient if (stbi__jpeg_get_bit(j)) data[0] += (short) (1 << j->succ_low); } return 1; } // @OPTIMIZE: store non-zigzagged during the decode passes, // and only de-zigzag when dequantizing static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) { int k; if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); if (j->succ_high == 0) { int shift = j->succ_low; if (j->eob_run) { --j->eob_run; return 1; } k = j->spec_start; do { unsigned int zig; int c,r,s; if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); r = fac[c]; if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length j->code_buffer <<= s; j->code_bits -= s; zig = stbi__jpeg_dezigzag[k++]; data[zig] = (short) ((r >> 8) << shift); } else { int rs = stbi__jpeg_huff_decode(j, hac); if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); s = rs & 15; r = rs >> 4; if (s == 0) { if (r < 15) { j->eob_run = (1 << r); if (r) j->eob_run += stbi__jpeg_get_bits(j, r); --j->eob_run; break; } k += 16; } else { k += r; zig = stbi__jpeg_dezigzag[k++]; data[zig] = (short) (stbi__extend_receive(j,s) << shift); } } } while (k <= j->spec_end); } else { // refinement scan for these AC coefficients short bit = (short) (1 << j->succ_low); if (j->eob_run) { --j->eob_run; for (k = j->spec_start; k <= j->spec_end; ++k) { short *p = &data[stbi__jpeg_dezigzag[k]]; if (*p != 0) if (stbi__jpeg_get_bit(j)) if ((*p & bit)==0) { if (*p > 0) *p += bit; else *p -= bit; } } } else { k = j->spec_start; do { int r,s; int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); s = rs & 15; r = rs >> 4; if (s == 0) { if (r < 15) { j->eob_run = (1 << r) - 1; if (r) j->eob_run += stbi__jpeg_get_bits(j, r); r = 64; // force end of block } else { // r=15 s=0 should write 16 0s, so we just do // a run of 15 0s and then write s (which is 0), // so we don't have to do anything special here } } else { if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); // sign bit if (stbi__jpeg_get_bit(j)) s = bit; else s = -bit; } // advance by r while (k <= j->spec_end) { short *p = &data[stbi__jpeg_dezigzag[k++]]; if (*p != 0) { if (stbi__jpeg_get_bit(j)) if ((*p & bit)==0) { if (*p > 0) *p += bit; else *p -= bit; } } else { if (r == 0) { *p = (short) s; break; } --r; } } } while (k <= j->spec_end); } } return 1; } // take a -128..127 value and stbi__clamp it and convert to 0..255 stbi_inline static stbi_uc stbi__clamp(int x) { // trick to use a single test to catch both cases if ((unsigned int) x > 255) { if (x < 0) return 0; if (x > 255) return 255; } return (stbi_uc) x; } #define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) #define stbi__fsh(x) ((x) << 12) // derived from jidctint -- DCT_ISLOW #define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ p2 = s2; \ p3 = s6; \ p1 = (p2+p3) * stbi__f2f(0.5411961f); \ t2 = p1 + p3*stbi__f2f(-1.847759065f); \ t3 = p1 + p2*stbi__f2f( 0.765366865f); \ p2 = s0; \ p3 = s4; \ t0 = stbi__fsh(p2+p3); \ t1 = stbi__fsh(p2-p3); \ x0 = t0+t3; \ x3 = t0-t3; \ x1 = t1+t2; \ x2 = t1-t2; \ t0 = s7; \ t1 = s5; \ t2 = s3; \ t3 = s1; \ p3 = t0+t2; \ p4 = t1+t3; \ p1 = t0+t3; \ p2 = t1+t2; \ p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ t0 = t0*stbi__f2f( 0.298631336f); \ t1 = t1*stbi__f2f( 2.053119869f); \ t2 = t2*stbi__f2f( 3.072711026f); \ t3 = t3*stbi__f2f( 1.501321110f); \ p1 = p5 + p1*stbi__f2f(-0.899976223f); \ p2 = p5 + p2*stbi__f2f(-2.562915447f); \ p3 = p3*stbi__f2f(-1.961570560f); \ p4 = p4*stbi__f2f(-0.390180644f); \ t3 += p1+p4; \ t2 += p2+p3; \ t1 += p2+p4; \ t0 += p1+p3; static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) { int i,val[64],*v=val; stbi_uc *o; short *d = data; // columns for (i=0; i < 8; ++i,++d, ++v) { // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 && d[40]==0 && d[48]==0 && d[56]==0) { // no shortcut 0 seconds // (1|2|3|4|5|6|7)==0 0 seconds // all separate -0.047 seconds // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds int dcterm = d[0] << 2; v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; } else { STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) // constants scaled things up by 1<<12; let's bring them back // down, but keep 2 extra bits of precision x0 += 512; x1 += 512; x2 += 512; x3 += 512; v[ 0] = (x0+t3) >> 10; v[56] = (x0-t3) >> 10; v[ 8] = (x1+t2) >> 10; v[48] = (x1-t2) >> 10; v[16] = (x2+t1) >> 10; v[40] = (x2-t1) >> 10; v[24] = (x3+t0) >> 10; v[32] = (x3-t0) >> 10; } } for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { // no fast case since the first 1D IDCT spread components out STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) // constants scaled things up by 1<<12, plus we had 1<<2 from first // loop, plus horizontal and vertical each scale by sqrt(8) so together // we've got an extra 1<<3, so 1<<17 total we need to remove. // so we want to round that, which means adding 0.5 * 1<<17, // aka 65536. Also, we'll end up with -128 to 127 that we want // to encode as 0..255 by adding 128, so we'll add that before the shift x0 += 65536 + (128<<17); x1 += 65536 + (128<<17); x2 += 65536 + (128<<17); x3 += 65536 + (128<<17); // tried computing the shifts into temps, or'ing the temps to see // if any were out of range, but that was slower o[0] = stbi__clamp((x0+t3) >> 17); o[7] = stbi__clamp((x0-t3) >> 17); o[1] = stbi__clamp((x1+t2) >> 17); o[6] = stbi__clamp((x1-t2) >> 17); o[2] = stbi__clamp((x2+t1) >> 17); o[5] = stbi__clamp((x2-t1) >> 17); o[3] = stbi__clamp((x3+t0) >> 17); o[4] = stbi__clamp((x3-t0) >> 17); } } #ifdef STBI_SSE2 // sse2 integer IDCT. not the fastest possible implementation but it // produces bit-identical results to the generic C version so it's // fully "transparent". static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) { // This is constructed to match our regular (generic) integer IDCT exactly. __m128i row0, row1, row2, row3, row4, row5, row6, row7; __m128i tmp; // dot product constant: even elems=x, odd elems=y #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) // out(1) = c1[even]*x + c1[odd]*y #define dct_rot(out0,out1, x,y,c0,c1) \ __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) // out = in << 12 (in 16-bit, out 32-bit) #define dct_widen(out, in) \ __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) // wide add #define dct_wadd(out, a, b) \ __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ __m128i out##_h = _mm_add_epi32(a##_h, b##_h) // wide sub #define dct_wsub(out, a, b) \ __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) // butterfly a/b, add bias, then shift by "s" and pack #define dct_bfly32o(out0, out1, a,b,bias,s) \ { \ __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ dct_wadd(sum, abiased, b); \ dct_wsub(dif, abiased, b); \ out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ } // 8-bit interleave step (for transposes) #define dct_interleave8(a, b) \ tmp = a; \ a = _mm_unpacklo_epi8(a, b); \ b = _mm_unpackhi_epi8(tmp, b) // 16-bit interleave step (for transposes) #define dct_interleave16(a, b) \ tmp = a; \ a = _mm_unpacklo_epi16(a, b); \ b = _mm_unpackhi_epi16(tmp, b) #define dct_pass(bias,shift) \ { \ /* even part */ \ dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ __m128i sum04 = _mm_add_epi16(row0, row4); \ __m128i dif04 = _mm_sub_epi16(row0, row4); \ dct_widen(t0e, sum04); \ dct_widen(t1e, dif04); \ dct_wadd(x0, t0e, t3e); \ dct_wsub(x3, t0e, t3e); \ dct_wadd(x1, t1e, t2e); \ dct_wsub(x2, t1e, t2e); \ /* odd part */ \ dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ __m128i sum17 = _mm_add_epi16(row1, row7); \ __m128i sum35 = _mm_add_epi16(row3, row5); \ dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ dct_wadd(x4, y0o, y4o); \ dct_wadd(x5, y1o, y5o); \ dct_wadd(x6, y2o, y5o); \ dct_wadd(x7, y3o, y4o); \ dct_bfly32o(row0,row7, x0,x7,bias,shift); \ dct_bfly32o(row1,row6, x1,x6,bias,shift); \ dct_bfly32o(row2,row5, x2,x5,bias,shift); \ dct_bfly32o(row3,row4, x3,x4,bias,shift); \ } __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); // rounding biases in column/row passes, see stbi__idct_block for explanation. __m128i bias_0 = _mm_set1_epi32(512); __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); // load row0 = _mm_load_si128((const __m128i *) (data + 0*8)); row1 = _mm_load_si128((const __m128i *) (data + 1*8)); row2 = _mm_load_si128((const __m128i *) (data + 2*8)); row3 = _mm_load_si128((const __m128i *) (data + 3*8)); row4 = _mm_load_si128((const __m128i *) (data + 4*8)); row5 = _mm_load_si128((const __m128i *) (data + 5*8)); row6 = _mm_load_si128((const __m128i *) (data + 6*8)); row7 = _mm_load_si128((const __m128i *) (data + 7*8)); // column pass dct_pass(bias_0, 10); { // 16bit 8x8 transpose pass 1 dct_interleave16(row0, row4); dct_interleave16(row1, row5); dct_interleave16(row2, row6); dct_interleave16(row3, row7); // transpose pass 2 dct_interleave16(row0, row2); dct_interleave16(row1, row3); dct_interleave16(row4, row6); dct_interleave16(row5, row7); // transpose pass 3 dct_interleave16(row0, row1); dct_interleave16(row2, row3); dct_interleave16(row4, row5); dct_interleave16(row6, row7); } // row pass dct_pass(bias_1, 17); { // pack __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 __m128i p1 = _mm_packus_epi16(row2, row3); __m128i p2 = _mm_packus_epi16(row4, row5); __m128i p3 = _mm_packus_epi16(row6, row7); // 8bit 8x8 transpose pass 1 dct_interleave8(p0, p2); // a0e0a1e1... dct_interleave8(p1, p3); // c0g0c1g1... // transpose pass 2 dct_interleave8(p0, p1); // a0c0e0g0... dct_interleave8(p2, p3); // b0d0f0h0... // transpose pass 3 dct_interleave8(p0, p2); // a0b0c0d0... dct_interleave8(p1, p3); // a4b4c4d4... // store _mm_storel_epi64((__m128i *) out, p0); out += out_stride; _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; _mm_storel_epi64((__m128i *) out, p2); out += out_stride; _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; _mm_storel_epi64((__m128i *) out, p1); out += out_stride; _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; _mm_storel_epi64((__m128i *) out, p3); out += out_stride; _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); } #undef dct_const #undef dct_rot #undef dct_widen #undef dct_wadd #undef dct_wsub #undef dct_bfly32o #undef dct_interleave8 #undef dct_interleave16 #undef dct_pass } #endif // STBI_SSE2 #ifdef STBI_NEON // NEON integer IDCT. should produce bit-identical // results to the generic C version. static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) { int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); #define dct_long_mul(out, inq, coeff) \ int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) #define dct_long_mac(out, acc, inq, coeff) \ int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) #define dct_widen(out, inq) \ int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) // wide add #define dct_wadd(out, a, b) \ int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ int32x4_t out##_h = vaddq_s32(a##_h, b##_h) // wide sub #define dct_wsub(out, a, b) \ int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ int32x4_t out##_h = vsubq_s32(a##_h, b##_h) // butterfly a/b, then shift using "shiftop" by "s" and pack #define dct_bfly32o(out0,out1, a,b,shiftop,s) \ { \ dct_wadd(sum, a, b); \ dct_wsub(dif, a, b); \ out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ } #define dct_pass(shiftop, shift) \ { \ /* even part */ \ int16x8_t sum26 = vaddq_s16(row2, row6); \ dct_long_mul(p1e, sum26, rot0_0); \ dct_long_mac(t2e, p1e, row6, rot0_1); \ dct_long_mac(t3e, p1e, row2, rot0_2); \ int16x8_t sum04 = vaddq_s16(row0, row4); \ int16x8_t dif04 = vsubq_s16(row0, row4); \ dct_widen(t0e, sum04); \ dct_widen(t1e, dif04); \ dct_wadd(x0, t0e, t3e); \ dct_wsub(x3, t0e, t3e); \ dct_wadd(x1, t1e, t2e); \ dct_wsub(x2, t1e, t2e); \ /* odd part */ \ int16x8_t sum15 = vaddq_s16(row1, row5); \ int16x8_t sum17 = vaddq_s16(row1, row7); \ int16x8_t sum35 = vaddq_s16(row3, row5); \ int16x8_t sum37 = vaddq_s16(row3, row7); \ int16x8_t sumodd = vaddq_s16(sum17, sum35); \ dct_long_mul(p5o, sumodd, rot1_0); \ dct_long_mac(p1o, p5o, sum17, rot1_1); \ dct_long_mac(p2o, p5o, sum35, rot1_2); \ dct_long_mul(p3o, sum37, rot2_0); \ dct_long_mul(p4o, sum15, rot2_1); \ dct_wadd(sump13o, p1o, p3o); \ dct_wadd(sump24o, p2o, p4o); \ dct_wadd(sump23o, p2o, p3o); \ dct_wadd(sump14o, p1o, p4o); \ dct_long_mac(x4, sump13o, row7, rot3_0); \ dct_long_mac(x5, sump24o, row5, rot3_1); \ dct_long_mac(x6, sump23o, row3, rot3_2); \ dct_long_mac(x7, sump14o, row1, rot3_3); \ dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ } // load row0 = vld1q_s16(data + 0*8); row1 = vld1q_s16(data + 1*8); row2 = vld1q_s16(data + 2*8); row3 = vld1q_s16(data + 3*8); row4 = vld1q_s16(data + 4*8); row5 = vld1q_s16(data + 5*8); row6 = vld1q_s16(data + 6*8); row7 = vld1q_s16(data + 7*8); // add DC bias row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); // column pass dct_pass(vrshrn_n_s32, 10); // 16bit 8x8 transpose { // these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. // whether compilers actually get this is another story, sadly. #define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } #define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } #define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } // pass 1 dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 dct_trn16(row2, row3); dct_trn16(row4, row5); dct_trn16(row6, row7); // pass 2 dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 dct_trn32(row1, row3); dct_trn32(row4, row6); dct_trn32(row5, row7); // pass 3 dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 dct_trn64(row1, row5); dct_trn64(row2, row6); dct_trn64(row3, row7); #undef dct_trn16 #undef dct_trn32 #undef dct_trn64 } // row pass // vrshrn_n_s32 only supports shifts up to 16, we need // 17. so do a non-rounding shift of 16 first then follow // up with a rounding shift by 1. dct_pass(vshrn_n_s32, 16); { // pack and round uint8x8_t p0 = vqrshrun_n_s16(row0, 1); uint8x8_t p1 = vqrshrun_n_s16(row1, 1); uint8x8_t p2 = vqrshrun_n_s16(row2, 1); uint8x8_t p3 = vqrshrun_n_s16(row3, 1); uint8x8_t p4 = vqrshrun_n_s16(row4, 1); uint8x8_t p5 = vqrshrun_n_s16(row5, 1); uint8x8_t p6 = vqrshrun_n_s16(row6, 1); uint8x8_t p7 = vqrshrun_n_s16(row7, 1); // again, these can translate into one instruction, but often don't. #define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } #define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } #define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } // sadly can't use interleaved stores here since we only write // 8 bytes to each scan line! // 8x8 8-bit transpose pass 1 dct_trn8_8(p0, p1); dct_trn8_8(p2, p3); dct_trn8_8(p4, p5); dct_trn8_8(p6, p7); // pass 2 dct_trn8_16(p0, p2); dct_trn8_16(p1, p3); dct_trn8_16(p4, p6); dct_trn8_16(p5, p7); // pass 3 dct_trn8_32(p0, p4); dct_trn8_32(p1, p5); dct_trn8_32(p2, p6); dct_trn8_32(p3, p7); // store vst1_u8(out, p0); out += out_stride; vst1_u8(out, p1); out += out_stride; vst1_u8(out, p2); out += out_stride; vst1_u8(out, p3); out += out_stride; vst1_u8(out, p4); out += out_stride; vst1_u8(out, p5); out += out_stride; vst1_u8(out, p6); out += out_stride; vst1_u8(out, p7); #undef dct_trn8_8 #undef dct_trn8_16 #undef dct_trn8_32 } #undef dct_long_mul #undef dct_long_mac #undef dct_widen #undef dct_wadd #undef dct_wsub #undef dct_bfly32o #undef dct_pass } #endif // STBI_NEON #define STBI__MARKER_none 0xff // if there's a pending marker from the entropy stream, return that // otherwise, fetch from the stream and get a marker. if there's no // marker, return 0xff, which is never a valid marker value static stbi_uc stbi__get_marker(stbi__jpeg *j) { stbi_uc x; if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } x = stbi__get8(j->s); if (x != 0xff) return STBI__MARKER_none; while (x == 0xff) x = stbi__get8(j->s); // consume repeated 0xff fill bytes return x; } // in each scan, we'll have scan_n components, and the order // of the components is specified by order[] #define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) // after a restart interval, stbi__jpeg_reset the entropy decoder and // the dc prediction static void stbi__jpeg_reset(stbi__jpeg *j) { j->code_bits = 0; j->code_buffer = 0; j->nomore = 0; j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; j->marker = STBI__MARKER_none; j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; j->eob_run = 0; // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, // since we don't even allow 1<<30 pixels } static int stbi__parse_entropy_coded_data(stbi__jpeg *z) { stbi__jpeg_reset(z); if (!z->progressive) { if (z->scan_n == 1) { int i,j; STBI_SIMD_ALIGN(short, data[64]); int n = z->order[0]; // non-interleaved data, we just need to process one block at a time, // in trivial scanline order // number of blocks to do just depends on how many actual "pixels" this // component has, independent of interleaved MCU blocking and such int w = (z->img_comp[n].x+7) >> 3; int h = (z->img_comp[n].y+7) >> 3; for (j=0; j < h; ++j) { for (i=0; i < w; ++i) { int ha = z->img_comp[n].ha; if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); // every data block is an MCU, so countdown the restart interval if (--z->todo <= 0) { if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); // if it's NOT a restart, then just bail, so we get corrupt data // rather than no data if (!STBI__RESTART(z->marker)) return 1; stbi__jpeg_reset(z); } } } return 1; } else { // interleaved int i,j,k,x,y; STBI_SIMD_ALIGN(short, data[64]); for (j=0; j < z->img_mcu_y; ++j) { for (i=0; i < z->img_mcu_x; ++i) { // scan an interleaved mcu... process scan_n components in order for (k=0; k < z->scan_n; ++k) { int n = z->order[k]; // scan out an mcu's worth of this component; that's just determined // by the basic H and V specified for the component for (y=0; y < z->img_comp[n].v; ++y) { for (x=0; x < z->img_comp[n].h; ++x) { int x2 = (i*z->img_comp[n].h + x)*8; int y2 = (j*z->img_comp[n].v + y)*8; int ha = z->img_comp[n].ha; if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); } } } // after all interleaved components, that's an interleaved MCU, // so now count down the restart interval if (--z->todo <= 0) { if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); if (!STBI__RESTART(z->marker)) return 1; stbi__jpeg_reset(z); } } } return 1; } } else { if (z->scan_n == 1) { int i,j; int n = z->order[0]; // non-interleaved data, we just need to process one block at a time, // in trivial scanline order // number of blocks to do just depends on how many actual "pixels" this // component has, independent of interleaved MCU blocking and such int w = (z->img_comp[n].x+7) >> 3; int h = (z->img_comp[n].y+7) >> 3; for (j=0; j < h; ++j) { for (i=0; i < w; ++i) { short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); if (z->spec_start == 0) { if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) return 0; } else { int ha = z->img_comp[n].ha; if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) return 0; } // every data block is an MCU, so countdown the restart interval if (--z->todo <= 0) { if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); if (!STBI__RESTART(z->marker)) return 1; stbi__jpeg_reset(z); } } } return 1; } else { // interleaved int i,j,k,x,y; for (j=0; j < z->img_mcu_y; ++j) { for (i=0; i < z->img_mcu_x; ++i) { // scan an interleaved mcu... process scan_n components in order for (k=0; k < z->scan_n; ++k) { int n = z->order[k]; // scan out an mcu's worth of this component; that's just determined // by the basic H and V specified for the component for (y=0; y < z->img_comp[n].v; ++y) { for (x=0; x < z->img_comp[n].h; ++x) { int x2 = (i*z->img_comp[n].h + x); int y2 = (j*z->img_comp[n].v + y); short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) return 0; } } } // after all interleaved components, that's an interleaved MCU, // so now count down the restart interval if (--z->todo <= 0) { if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); if (!STBI__RESTART(z->marker)) return 1; stbi__jpeg_reset(z); } } } return 1; } } } static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) { int i; for (i=0; i < 64; ++i) data[i] *= dequant[i]; } static void stbi__jpeg_finish(stbi__jpeg *z) { if (z->progressive) { // dequantize and idct the data int i,j,n; for (n=0; n < z->s->img_n; ++n) { int w = (z->img_comp[n].x+7) >> 3; int h = (z->img_comp[n].y+7) >> 3; for (j=0; j < h; ++j) { for (i=0; i < w; ++i) { short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); } } } } } static int stbi__process_marker(stbi__jpeg *z, int m) { int L; switch (m) { case STBI__MARKER_none: // no marker found return stbi__err("expected marker","Corrupt JPEG"); case 0xDD: // DRI - specify restart interval if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); z->restart_interval = stbi__get16be(z->s); return 1; case 0xDB: // DQT - define quantization table L = stbi__get16be(z->s)-2; while (L > 0) { int q = stbi__get8(z->s); int p = q >> 4, sixteen = (p != 0); int t = q & 15,i; if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); for (i=0; i < 64; ++i) z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); L -= (sixteen ? 129 : 65); } return L==0; case 0xC4: // DHT - define huffman table L = stbi__get16be(z->s)-2; while (L > 0) { stbi_uc *v; int sizes[16],i,n=0; int q = stbi__get8(z->s); int tc = q >> 4; int th = q & 15; if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); for (i=0; i < 16; ++i) { sizes[i] = stbi__get8(z->s); n += sizes[i]; } L -= 17; if (tc == 0) { if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; v = z->huff_dc[th].values; } else { if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; v = z->huff_ac[th].values; } for (i=0; i < n; ++i) v[i] = stbi__get8(z->s); if (tc != 0) stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); L -= n; } return L==0; } // check for comment block or APP blocks if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { L = stbi__get16be(z->s); if (L < 2) { if (m == 0xFE) return stbi__err("bad COM len","Corrupt JPEG"); else return stbi__err("bad APP len","Corrupt JPEG"); } L -= 2; if (m == 0xE0 && L >= 5) { // JFIF APP0 segment static const unsigned char tag[5] = {'J','F','I','F','\0'}; int ok = 1; int i; for (i=0; i < 5; ++i) if (stbi__get8(z->s) != tag[i]) ok = 0; L -= 5; if (ok) z->jfif = 1; } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; int ok = 1; int i; for (i=0; i < 6; ++i) if (stbi__get8(z->s) != tag[i]) ok = 0; L -= 6; if (ok) { stbi__get8(z->s); // version stbi__get16be(z->s); // flags0 stbi__get16be(z->s); // flags1 z->app14_color_transform = stbi__get8(z->s); // color transform L -= 6; } } stbi__skip(z->s, L); return 1; } return stbi__err("unknown marker","Corrupt JPEG"); } // after we see SOS static int stbi__process_scan_header(stbi__jpeg *z) { int i; int Ls = stbi__get16be(z->s); z->scan_n = stbi__get8(z->s); if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); for (i=0; i < z->scan_n; ++i) { int id = stbi__get8(z->s), which; int q = stbi__get8(z->s); for (which = 0; which < z->s->img_n; ++which) if (z->img_comp[which].id == id) break; if (which == z->s->img_n) return 0; // no match z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); z->order[i] = which; } { int aa; z->spec_start = stbi__get8(z->s); z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 aa = stbi__get8(z->s); z->succ_high = (aa >> 4); z->succ_low = (aa & 15); if (z->progressive) { if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) return stbi__err("bad SOS", "Corrupt JPEG"); } else { if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); z->spec_end = 63; } } return 1; } static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) { int i; for (i=0; i < ncomp; ++i) { if (z->img_comp[i].raw_data) { STBI_FREE(z->img_comp[i].raw_data); z->img_comp[i].raw_data = NULL; z->img_comp[i].data = NULL; } if (z->img_comp[i].raw_coeff) { STBI_FREE(z->img_comp[i].raw_coeff); z->img_comp[i].raw_coeff = 0; z->img_comp[i].coeff = 0; } if (z->img_comp[i].linebuf) { STBI_FREE(z->img_comp[i].linebuf); z->img_comp[i].linebuf = NULL; } } return why; } static int stbi__process_frame_header(stbi__jpeg *z, int scan) { stbi__context *s = z->s; int Lf,p,i,q, h_max=1,v_max=1,c; Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires c = stbi__get8(s); if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); s->img_n = c; for (i=0; i < c; ++i) { z->img_comp[i].data = NULL; z->img_comp[i].linebuf = NULL; } if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); z->rgb = 0; for (i=0; i < s->img_n; ++i) { static unsigned char rgb[3] = { 'R', 'G', 'B' }; z->img_comp[i].id = stbi__get8(s); if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) ++z->rgb; q = stbi__get8(s); z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); } if (scan != STBI__SCAN_load) return 1; if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); for (i=0; i < s->img_n; ++i) { if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; } // compute interleaved mcu info z->img_h_max = h_max; z->img_v_max = v_max; z->img_mcu_w = h_max * 8; z->img_mcu_h = v_max * 8; // these sizes can't be more than 17 bits z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; for (i=0; i < s->img_n; ++i) { // number of effective pixels (e.g. for non-interleaved MCU) z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; // to simplify generation, we'll allocate enough memory to decode // the bogus oversized data from using interleaved MCUs and their // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't // discard the extra data until colorspace conversion // // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) // so these muls can't overflow with 32-bit ints (which we require) z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; z->img_comp[i].coeff = 0; z->img_comp[i].raw_coeff = 0; z->img_comp[i].linebuf = NULL; z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); if (z->img_comp[i].raw_data == NULL) return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); // align blocks for idct using mmx/sse z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); if (z->progressive) { // w2, h2 are multiples of 8 (see above) z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); if (z->img_comp[i].raw_coeff == NULL) return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); } } return 1; } // use comparisons since in some cases we handle more than one case (e.g. SOF) #define stbi__DNL(x) ((x) == 0xdc) #define stbi__SOI(x) ((x) == 0xd8) #define stbi__EOI(x) ((x) == 0xd9) #define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) #define stbi__SOS(x) ((x) == 0xda) #define stbi__SOF_progressive(x) ((x) == 0xc2) static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) { int m; z->jfif = 0; z->app14_color_transform = -1; // valid values are 0,1,2 z->marker = STBI__MARKER_none; // initialize cached marker to empty m = stbi__get_marker(z); if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); if (scan == STBI__SCAN_type) return 1; m = stbi__get_marker(z); while (!stbi__SOF(m)) { if (!stbi__process_marker(z,m)) return 0; m = stbi__get_marker(z); while (m == STBI__MARKER_none) { // some files have extra padding after their blocks, so ok, we'll scan if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); m = stbi__get_marker(z); } } z->progressive = stbi__SOF_progressive(m); if (!stbi__process_frame_header(z, scan)) return 0; return 1; } // decode image to YCbCr format static int stbi__decode_jpeg_image(stbi__jpeg *j) { int m; for (m = 0; m < 4; m++) { j->img_comp[m].raw_data = NULL; j->img_comp[m].raw_coeff = NULL; } j->restart_interval = 0; if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; m = stbi__get_marker(j); while (!stbi__EOI(m)) { if (stbi__SOS(m)) { if (!stbi__process_scan_header(j)) return 0; if (!stbi__parse_entropy_coded_data(j)) return 0; if (j->marker == STBI__MARKER_none ) { // handle 0s at the end of image data from IP Kamera 9060 while (!stbi__at_eof(j->s)) { int x = stbi__get8(j->s); if (x == 255) { j->marker = stbi__get8(j->s); break; } } // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 } } else if (stbi__DNL(m)) { int Ld = stbi__get16be(j->s); stbi__uint32 NL = stbi__get16be(j->s); if (Ld != 4) stbi__err("bad DNL len", "Corrupt JPEG"); if (NL != j->s->img_y) stbi__err("bad DNL height", "Corrupt JPEG"); } else { if (!stbi__process_marker(j, m)) return 0; } m = stbi__get_marker(j); } if (j->progressive) stbi__jpeg_finish(j); return 1; } // static jfif-centered resampling (across block boundaries) typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, int w, int hs); #define stbi__div4(x) ((stbi_uc) ((x) >> 2)) static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { STBI_NOTUSED(out); STBI_NOTUSED(in_far); STBI_NOTUSED(w); STBI_NOTUSED(hs); return in_near; } static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // need to generate two samples vertically for every one in input int i; STBI_NOTUSED(hs); for (i=0; i < w; ++i) out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); return out; } static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // need to generate two samples horizontally for every one in input int i; stbi_uc *input = in_near; if (w == 1) { // if only one sample, can't do any interpolation out[0] = out[1] = input[0]; return out; } out[0] = input[0]; out[1] = stbi__div4(input[0]*3 + input[1] + 2); for (i=1; i < w-1; ++i) { int n = 3*input[i]+2; out[i*2+0] = stbi__div4(n+input[i-1]); out[i*2+1] = stbi__div4(n+input[i+1]); } out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); out[i*2+1] = input[w-1]; STBI_NOTUSED(in_far); STBI_NOTUSED(hs); return out; } #define stbi__div16(x) ((stbi_uc) ((x) >> 4)) static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // need to generate 2x2 samples for every one in input int i,t0,t1; if (w == 1) { out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); return out; } t1 = 3*in_near[0] + in_far[0]; out[0] = stbi__div4(t1+2); for (i=1; i < w; ++i) { t0 = t1; t1 = 3*in_near[i]+in_far[i]; out[i*2-1] = stbi__div16(3*t0 + t1 + 8); out[i*2 ] = stbi__div16(3*t1 + t0 + 8); } out[w*2-1] = stbi__div4(t1+2); STBI_NOTUSED(hs); return out; } #if defined(STBI_SSE2) || defined(STBI_NEON) static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // need to generate 2x2 samples for every one in input int i=0,t0,t1; if (w == 1) { out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); return out; } t1 = 3*in_near[0] + in_far[0]; // process groups of 8 pixels for as long as we can. // note we can't handle the last pixel in a row in this loop // because we need to handle the filter boundary conditions. for (; i < ((w-1) & ~7); i += 8) { #if defined(STBI_SSE2) // load and perform the vertical filtering pass // this uses 3*x + y = 4*x + (y - x) __m128i zero = _mm_setzero_si128(); __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); __m128i farw = _mm_unpacklo_epi8(farb, zero); __m128i nearw = _mm_unpacklo_epi8(nearb, zero); __m128i diff = _mm_sub_epi16(farw, nearw); __m128i nears = _mm_slli_epi16(nearw, 2); __m128i curr = _mm_add_epi16(nears, diff); // current row // horizontal filter works the same based on shifted vers of current // row. "prev" is current row shifted right by 1 pixel; we need to // insert the previous pixel value (from t1). // "next" is current row shifted left by 1 pixel, with first pixel // of next block of 8 pixels added in. __m128i prv0 = _mm_slli_si128(curr, 2); __m128i nxt0 = _mm_srli_si128(curr, 2); __m128i prev = _mm_insert_epi16(prv0, t1, 0); __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); // horizontal filter, polyphase implementation since it's convenient: // even pixels = 3*cur + prev = cur*4 + (prev - cur) // odd pixels = 3*cur + next = cur*4 + (next - cur) // note the shared term. __m128i bias = _mm_set1_epi16(8); __m128i curs = _mm_slli_epi16(curr, 2); __m128i prvd = _mm_sub_epi16(prev, curr); __m128i nxtd = _mm_sub_epi16(next, curr); __m128i curb = _mm_add_epi16(curs, bias); __m128i even = _mm_add_epi16(prvd, curb); __m128i odd = _mm_add_epi16(nxtd, curb); // interleave even and odd pixels, then undo scaling. __m128i int0 = _mm_unpacklo_epi16(even, odd); __m128i int1 = _mm_unpackhi_epi16(even, odd); __m128i de0 = _mm_srli_epi16(int0, 4); __m128i de1 = _mm_srli_epi16(int1, 4); // pack and write output __m128i outv = _mm_packus_epi16(de0, de1); _mm_storeu_si128((__m128i *) (out + i*2), outv); #elif defined(STBI_NEON) // load and perform the vertical filtering pass // this uses 3*x + y = 4*x + (y - x) uint8x8_t farb = vld1_u8(in_far + i); uint8x8_t nearb = vld1_u8(in_near + i); int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); int16x8_t curr = vaddq_s16(nears, diff); // current row // horizontal filter works the same based on shifted vers of current // row. "prev" is current row shifted right by 1 pixel; we need to // insert the previous pixel value (from t1). // "next" is current row shifted left by 1 pixel, with first pixel // of next block of 8 pixels added in. int16x8_t prv0 = vextq_s16(curr, curr, 7); int16x8_t nxt0 = vextq_s16(curr, curr, 1); int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); // horizontal filter, polyphase implementation since it's convenient: // even pixels = 3*cur + prev = cur*4 + (prev - cur) // odd pixels = 3*cur + next = cur*4 + (next - cur) // note the shared term. int16x8_t curs = vshlq_n_s16(curr, 2); int16x8_t prvd = vsubq_s16(prev, curr); int16x8_t nxtd = vsubq_s16(next, curr); int16x8_t even = vaddq_s16(curs, prvd); int16x8_t odd = vaddq_s16(curs, nxtd); // undo scaling and round, then store with even/odd phases interleaved uint8x8x2_t o; o.val[0] = vqrshrun_n_s16(even, 4); o.val[1] = vqrshrun_n_s16(odd, 4); vst2_u8(out + i*2, o); #endif // "previous" value for next iter t1 = 3*in_near[i+7] + in_far[i+7]; } t0 = t1; t1 = 3*in_near[i] + in_far[i]; out[i*2] = stbi__div16(3*t1 + t0 + 8); for (++i; i < w; ++i) { t0 = t1; t1 = 3*in_near[i]+in_far[i]; out[i*2-1] = stbi__div16(3*t0 + t1 + 8); out[i*2 ] = stbi__div16(3*t1 + t0 + 8); } out[w*2-1] = stbi__div4(t1+2); STBI_NOTUSED(hs); return out; } #endif static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // resample with nearest-neighbor int i,j; STBI_NOTUSED(in_far); for (i=0; i < w; ++i) for (j=0; j < hs; ++j) out[i*hs+j] = in_near[i]; return out; } // this is a reduced-precision calculation of YCbCr-to-RGB introduced // to make sure the code produces the same results in both SIMD and scalar #define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) { int i; for (i=0; i < count; ++i) { int y_fixed = (y[i] << 20) + (1<<19); // rounding int r,g,b; int cr = pcr[i] - 128; int cb = pcb[i] - 128; r = y_fixed + cr* stbi__float2fixed(1.40200f); g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); b = y_fixed + cb* stbi__float2fixed(1.77200f); r >>= 20; g >>= 20; b >>= 20; if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } out[0] = (stbi_uc)r; out[1] = (stbi_uc)g; out[2] = (stbi_uc)b; out[3] = 255; out += step; } } #if defined(STBI_SSE2) || defined(STBI_NEON) static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) { int i = 0; #ifdef STBI_SSE2 // step == 3 is pretty ugly on the final interleave, and i'm not convinced // it's useful in practice (you wouldn't use it for textures, for example). // so just accelerate step == 4 case. if (step == 4) { // this is a fairly straightforward implementation and not super-optimized. __m128i signflip = _mm_set1_epi8(-0x80); __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); __m128i xw = _mm_set1_epi16(255); // alpha channel for (; i+7 < count; i += 8) { // load __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 // unpack to short (and left-shift cr, cb by 8) __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); // color transform __m128i yws = _mm_srli_epi16(yw, 4); __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); __m128i rws = _mm_add_epi16(cr0, yws); __m128i gwt = _mm_add_epi16(cb0, yws); __m128i bws = _mm_add_epi16(yws, cb1); __m128i gws = _mm_add_epi16(gwt, cr1); // descale __m128i rw = _mm_srai_epi16(rws, 4); __m128i bw = _mm_srai_epi16(bws, 4); __m128i gw = _mm_srai_epi16(gws, 4); // back to byte, set up for transpose __m128i brb = _mm_packus_epi16(rw, bw); __m128i gxb = _mm_packus_epi16(gw, xw); // transpose to interleave channels __m128i t0 = _mm_unpacklo_epi8(brb, gxb); __m128i t1 = _mm_unpackhi_epi8(brb, gxb); __m128i o0 = _mm_unpacklo_epi16(t0, t1); __m128i o1 = _mm_unpackhi_epi16(t0, t1); // store _mm_storeu_si128((__m128i *) (out + 0), o0); _mm_storeu_si128((__m128i *) (out + 16), o1); out += 32; } } #endif #ifdef STBI_NEON // in this version, step=3 support would be easy to add. but is there demand? if (step == 4) { // this is a fairly straightforward implementation and not super-optimized. uint8x8_t signflip = vdup_n_u8(0x80); int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); for (; i+7 < count; i += 8) { // load uint8x8_t y_bytes = vld1_u8(y + i); uint8x8_t cr_bytes = vld1_u8(pcr + i); uint8x8_t cb_bytes = vld1_u8(pcb + i); int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); // expand to s16 int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); int16x8_t crw = vshll_n_s8(cr_biased, 7); int16x8_t cbw = vshll_n_s8(cb_biased, 7); // color transform int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); int16x8_t rws = vaddq_s16(yws, cr0); int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); int16x8_t bws = vaddq_s16(yws, cb1); // undo scaling, round, convert to byte uint8x8x4_t o; o.val[0] = vqrshrun_n_s16(rws, 4); o.val[1] = vqrshrun_n_s16(gws, 4); o.val[2] = vqrshrun_n_s16(bws, 4); o.val[3] = vdup_n_u8(255); // store, interleaving r/g/b/a vst4_u8(out, o); out += 8*4; } } #endif for (; i < count; ++i) { int y_fixed = (y[i] << 20) + (1<<19); // rounding int r,g,b; int cr = pcr[i] - 128; int cb = pcb[i] - 128; r = y_fixed + cr* stbi__float2fixed(1.40200f); g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); b = y_fixed + cb* stbi__float2fixed(1.77200f); r >>= 20; g >>= 20; b >>= 20; if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } out[0] = (stbi_uc)r; out[1] = (stbi_uc)g; out[2] = (stbi_uc)b; out[3] = 255; out += step; } } #endif // set up the kernels static void stbi__setup_jpeg(stbi__jpeg *j) { j->idct_block_kernel = stbi__idct_block; j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; #ifdef STBI_SSE2 if (stbi__sse2_available()) { j->idct_block_kernel = stbi__idct_simd; j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; } #endif #ifdef STBI_NEON j->idct_block_kernel = stbi__idct_simd; j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; #endif } // clean up the temporary component buffers static void stbi__cleanup_jpeg(stbi__jpeg *j) { stbi__free_jpeg_components(j, j->s->img_n, 0); } typedef struct { resample_row_func resample; stbi_uc *line0,*line1; int hs,vs; // expansion factor in each axis int w_lores; // horizontal pixels pre-expansion int ystep; // how far through vertical expansion we are int ypos; // which pre-expansion row we're on } stbi__resample; // fast 0..255 * 0..255 => 0..255 rounded multiplication static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) { unsigned int t = x*y + 128; return (stbi_uc) ((t + (t >>8)) >> 8); } static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) { int n, decode_n, is_rgb; z->s->img_n = 0; // make stbi__cleanup_jpeg safe // validate req_comp if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); // load a jpeg image from whichever source, but leave in YCbCr format if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } // determine actual number of components to generate n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); if (z->s->img_n == 3 && n < 3 && !is_rgb) decode_n = 1; else decode_n = z->s->img_n; // resample and color-convert { int k; unsigned int i,j; stbi_uc *output; stbi_uc *coutput[4]; stbi__resample res_comp[4]; for (k=0; k < decode_n; ++k) { stbi__resample *r = &res_comp[k]; // allocate line buffer big enough for upsampling off the edges // with upsample factor of 4 z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } r->hs = z->img_h_max / z->img_comp[k].h; r->vs = z->img_v_max / z->img_comp[k].v; r->ystep = r->vs >> 1; r->w_lores = (z->s->img_x + r->hs-1) / r->hs; r->ypos = 0; r->line0 = r->line1 = z->img_comp[k].data; if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; else r->resample = stbi__resample_row_generic; } // can't error after this so, this is safe output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } // now go ahead and resample for (j=0; j < z->s->img_y; ++j) { stbi_uc *out = output + n * z->s->img_x * j; for (k=0; k < decode_n; ++k) { stbi__resample *r = &res_comp[k]; int y_bot = r->ystep >= (r->vs >> 1); coutput[k] = r->resample(z->img_comp[k].linebuf, y_bot ? r->line1 : r->line0, y_bot ? r->line0 : r->line1, r->w_lores, r->hs); if (++r->ystep >= r->vs) { r->ystep = 0; r->line0 = r->line1; if (++r->ypos < z->img_comp[k].y) r->line1 += z->img_comp[k].w2; } } if (n >= 3) { stbi_uc *y = coutput[0]; if (z->s->img_n == 3) { if (is_rgb) { for (i=0; i < z->s->img_x; ++i) { out[0] = y[i]; out[1] = coutput[1][i]; out[2] = coutput[2][i]; out[3] = 255; out += n; } } else { z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); } } else if (z->s->img_n == 4) { if (z->app14_color_transform == 0) { // CMYK for (i=0; i < z->s->img_x; ++i) { stbi_uc m = coutput[3][i]; out[0] = stbi__blinn_8x8(coutput[0][i], m); out[1] = stbi__blinn_8x8(coutput[1][i], m); out[2] = stbi__blinn_8x8(coutput[2][i], m); out[3] = 255; out += n; } } else if (z->app14_color_transform == 2) { // YCCK z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); for (i=0; i < z->s->img_x; ++i) { stbi_uc m = coutput[3][i]; out[0] = stbi__blinn_8x8(255 - out[0], m); out[1] = stbi__blinn_8x8(255 - out[1], m); out[2] = stbi__blinn_8x8(255 - out[2], m); out += n; } } else { // YCbCr + alpha? Ignore the fourth channel for now z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); } } else for (i=0; i < z->s->img_x; ++i) { out[0] = out[1] = out[2] = y[i]; out[3] = 255; // not used if n==3 out += n; } } else { if (is_rgb) { if (n == 1) for (i=0; i < z->s->img_x; ++i) *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); else { for (i=0; i < z->s->img_x; ++i, out += 2) { out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); out[1] = 255; } } } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { for (i=0; i < z->s->img_x; ++i) { stbi_uc m = coutput[3][i]; stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); out[0] = stbi__compute_y(r, g, b); out[1] = 255; out += n; } } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { for (i=0; i < z->s->img_x; ++i) { out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); out[1] = 255; out += n; } } else { stbi_uc *y = coutput[0]; if (n == 1) for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; else for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; } } } stbi__cleanup_jpeg(z); *out_x = z->s->img_x; *out_y = z->s->img_y; if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output return output; } } static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { unsigned char* result; stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); STBI_NOTUSED(ri); j->s = s; stbi__setup_jpeg(j); result = load_jpeg_image(j, x,y,comp,req_comp); STBI_FREE(j); return result; } static int stbi__jpeg_test(stbi__context *s) { int r; stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); j->s = s; stbi__setup_jpeg(j); r = stbi__decode_jpeg_header(j, STBI__SCAN_type); stbi__rewind(s); STBI_FREE(j); return r; } static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) { if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { stbi__rewind( j->s ); return 0; } if (x) *x = j->s->img_x; if (y) *y = j->s->img_y; if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; return 1; } static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) { int result; stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); j->s = s; result = stbi__jpeg_info_raw(j, x, y, comp); STBI_FREE(j); return result; } #endif // public domain zlib decode v0.2 Sean Barrett 2006-11-18 // simple implementation // - all input must be provided in an upfront buffer // - all output is written to a single output buffer (can malloc/realloc) // performance // - fast huffman #ifndef STBI_NO_ZLIB // fast-way is faster to check than jpeg huffman, but slow way is slower #define STBI__ZFAST_BITS 9 // accelerate all cases in default tables #define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) // zlib-style huffman encoding // (jpegs packs from left, zlib from right, so can't share code) typedef struct { stbi__uint16 fast[1 << STBI__ZFAST_BITS]; stbi__uint16 firstcode[16]; int maxcode[17]; stbi__uint16 firstsymbol[16]; stbi_uc size[288]; stbi__uint16 value[288]; } stbi__zhuffman; stbi_inline static int stbi__bitreverse16(int n) { n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); return n; } stbi_inline static int stbi__bit_reverse(int v, int bits) { STBI_ASSERT(bits <= 16); // to bit reverse n bits, reverse 16 and shift // e.g. 11 bits, bit reverse and shift away 5 return stbi__bitreverse16(v) >> (16-bits); } static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) { int i,k=0; int code, next_code[16], sizes[17]; // DEFLATE spec for generating codes memset(sizes, 0, sizeof(sizes)); memset(z->fast, 0, sizeof(z->fast)); for (i=0; i < num; ++i) ++sizes[sizelist[i]]; sizes[0] = 0; for (i=1; i < 16; ++i) if (sizes[i] > (1 << i)) return stbi__err("bad sizes", "Corrupt PNG"); code = 0; for (i=1; i < 16; ++i) { next_code[i] = code; z->firstcode[i] = (stbi__uint16) code; z->firstsymbol[i] = (stbi__uint16) k; code = (code + sizes[i]); if (sizes[i]) if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); z->maxcode[i] = code << (16-i); // preshift for inner loop code <<= 1; k += sizes[i]; } z->maxcode[16] = 0x10000; // sentinel for (i=0; i < num; ++i) { int s = sizelist[i]; if (s) { int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); z->size [c] = (stbi_uc ) s; z->value[c] = (stbi__uint16) i; if (s <= STBI__ZFAST_BITS) { int j = stbi__bit_reverse(next_code[s],s); while (j < (1 << STBI__ZFAST_BITS)) { z->fast[j] = fastv; j += (1 << s); } } ++next_code[s]; } } return 1; } // zlib-from-memory implementation for PNG reading // because PNG allows splitting the zlib stream arbitrarily, // and it's annoying structurally to have PNG call ZLIB call PNG, // we require PNG read all the IDATs and combine them into a single // memory buffer typedef struct { stbi_uc *zbuffer, *zbuffer_end; int num_bits; stbi__uint32 code_buffer; char *zout; char *zout_start; char *zout_end; int z_expandable; stbi__zhuffman z_length, z_distance; } stbi__zbuf; stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) { if (z->zbuffer >= z->zbuffer_end) return 0; return *z->zbuffer++; } static void stbi__fill_bits(stbi__zbuf *z) { do { STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; z->num_bits += 8; } while (z->num_bits <= 24); } stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) { unsigned int k; if (z->num_bits < n) stbi__fill_bits(z); k = z->code_buffer & ((1 << n) - 1); z->code_buffer >>= n; z->num_bits -= n; return k; } static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) { int b,s,k; // not resolved by fast table, so compute it the slow way // use jpeg approach, which requires MSbits at top k = stbi__bit_reverse(a->code_buffer, 16); for (s=STBI__ZFAST_BITS+1; ; ++s) if (k < z->maxcode[s]) break; if (s == 16) return -1; // invalid code! // code size is s, so: b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; STBI_ASSERT(z->size[b] == s); a->code_buffer >>= s; a->num_bits -= s; return z->value[b]; } stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) { int b,s; if (a->num_bits < 16) stbi__fill_bits(a); b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; if (b) { s = b >> 9; a->code_buffer >>= s; a->num_bits -= s; return b & 511; } return stbi__zhuffman_decode_slowpath(a, z); } static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes { char *q; int cur, limit, old_limit; z->zout = zout; if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); cur = (int) (z->zout - z->zout_start); limit = old_limit = (int) (z->zout_end - z->zout_start); while (cur + n > limit) limit *= 2; q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); STBI_NOTUSED(old_limit); if (q == NULL) return stbi__err("outofmem", "Out of memory"); z->zout_start = q; z->zout = q + cur; z->zout_end = q + limit; return 1; } static int stbi__zlength_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; static int stbi__zlength_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; static int stbi__zdist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; static int stbi__parse_huffman_block(stbi__zbuf *a) { char *zout = a->zout; for(;;) { int z = stbi__zhuffman_decode(a, &a->z_length); if (z < 256) { if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes if (zout >= a->zout_end) { if (!stbi__zexpand(a, zout, 1)) return 0; zout = a->zout; } *zout++ = (char) z; } else { stbi_uc *p; int len,dist; if (z == 256) { a->zout = zout; return 1; } z -= 257; len = stbi__zlength_base[z]; if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); z = stbi__zhuffman_decode(a, &a->z_distance); if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); if (zout + len > a->zout_end) { if (!stbi__zexpand(a, zout, len)) return 0; zout = a->zout; } p = (stbi_uc *) (zout - dist); if (dist == 1) { // run of one byte; common in images. stbi_uc v = *p; if (len) { do *zout++ = v; while (--len); } } else { if (len) { do *zout++ = *p++; while (--len); } } } } } static int stbi__compute_huffman_codes(stbi__zbuf *a) { static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; stbi__zhuffman z_codelength; stbi_uc lencodes[286+32+137];//padding for maximum single op stbi_uc codelength_sizes[19]; int i,n; int hlit = stbi__zreceive(a,5) + 257; int hdist = stbi__zreceive(a,5) + 1; int hclen = stbi__zreceive(a,4) + 4; int ntot = hlit + hdist; memset(codelength_sizes, 0, sizeof(codelength_sizes)); for (i=0; i < hclen; ++i) { int s = stbi__zreceive(a,3); codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; } if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; n = 0; while (n < ntot) { int c = stbi__zhuffman_decode(a, &z_codelength); if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); if (c < 16) lencodes[n++] = (stbi_uc) c; else { stbi_uc fill = 0; if (c == 16) { c = stbi__zreceive(a,2)+3; if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); fill = lencodes[n-1]; } else if (c == 17) c = stbi__zreceive(a,3)+3; else { STBI_ASSERT(c == 18); c = stbi__zreceive(a,7)+11; } if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); memset(lencodes+n, fill, c); n += c; } } if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; return 1; } static int stbi__parse_uncompressed_block(stbi__zbuf *a) { stbi_uc header[4]; int len,nlen,k; if (a->num_bits & 7) stbi__zreceive(a, a->num_bits & 7); // discard // drain the bit-packed data into header k = 0; while (a->num_bits > 0) { header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check a->code_buffer >>= 8; a->num_bits -= 8; } STBI_ASSERT(a->num_bits == 0); // now fill header the normal way while (k < 4) header[k++] = stbi__zget8(a); len = header[1] * 256 + header[0]; nlen = header[3] * 256 + header[2]; if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); if (a->zout + len > a->zout_end) if (!stbi__zexpand(a, a->zout, len)) return 0; memcpy(a->zout, a->zbuffer, len); a->zbuffer += len; a->zout += len; return 1; } static int stbi__parse_zlib_header(stbi__zbuf *a) { int cmf = stbi__zget8(a); int cm = cmf & 15; /* int cinfo = cmf >> 4; */ int flg = stbi__zget8(a); if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png // window = 1 << (8 + cinfo)... but who cares, we fully buffer output return 1; } static const stbi_uc stbi__zdefault_length[288] = { 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 }; static const stbi_uc stbi__zdefault_distance[32] = { 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 }; /* Init algorithm: { int i; // use <= to match clearly with spec for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; } */ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) { int final, type; if (parse_header) if (!stbi__parse_zlib_header(a)) return 0; a->num_bits = 0; a->code_buffer = 0; do { final = stbi__zreceive(a,1); type = stbi__zreceive(a,2); if (type == 0) { if (!stbi__parse_uncompressed_block(a)) return 0; } else if (type == 3) { return 0; } else { if (type == 1) { // use fixed code lengths if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; } else { if (!stbi__compute_huffman_codes(a)) return 0; } if (!stbi__parse_huffman_block(a)) return 0; } } while (!final); return 1; } static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) { a->zout_start = obuf; a->zout = obuf; a->zout_end = obuf + olen; a->z_expandable = exp; return stbi__parse_zlib(a, parse_header); } STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) { stbi__zbuf a; char *p = (char *) stbi__malloc(initial_size); if (p == NULL) return NULL; a.zbuffer = (stbi_uc *) buffer; a.zbuffer_end = (stbi_uc *) buffer + len; if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { if (outlen) *outlen = (int) (a.zout - a.zout_start); return a.zout_start; } else { STBI_FREE(a.zout_start); return NULL; } } STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) { return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); } STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) { stbi__zbuf a; char *p = (char *) stbi__malloc(initial_size); if (p == NULL) return NULL; a.zbuffer = (stbi_uc *) buffer; a.zbuffer_end = (stbi_uc *) buffer + len; if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { if (outlen) *outlen = (int) (a.zout - a.zout_start); return a.zout_start; } else { STBI_FREE(a.zout_start); return NULL; } } STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) { stbi__zbuf a; a.zbuffer = (stbi_uc *) ibuffer; a.zbuffer_end = (stbi_uc *) ibuffer + ilen; if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) return (int) (a.zout - a.zout_start); else return -1; } STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) { stbi__zbuf a; char *p = (char *) stbi__malloc(16384); if (p == NULL) return NULL; a.zbuffer = (stbi_uc *) buffer; a.zbuffer_end = (stbi_uc *) buffer+len; if (stbi__do_zlib(&a, p, 16384, 1, 0)) { if (outlen) *outlen = (int) (a.zout - a.zout_start); return a.zout_start; } else { STBI_FREE(a.zout_start); return NULL; } } STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) { stbi__zbuf a; a.zbuffer = (stbi_uc *) ibuffer; a.zbuffer_end = (stbi_uc *) ibuffer + ilen; if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) return (int) (a.zout - a.zout_start); else return -1; } #endif // public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 // simple implementation // - only 8-bit samples // - no CRC checking // - allocates lots of intermediate memory // - avoids problem of streaming data between subsystems // - avoids explicit window management // performance // - uses stb_zlib, a PD zlib implementation with fast huffman decoding #ifndef STBI_NO_PNG typedef struct { stbi__uint32 length; stbi__uint32 type; } stbi__pngchunk; static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) { stbi__pngchunk c; c.length = stbi__get32be(s); c.type = stbi__get32be(s); return c; } static int stbi__check_png_header(stbi__context *s) { static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; int i; for (i=0; i < 8; ++i) if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); return 1; } typedef struct { stbi__context *s; stbi_uc *idata, *expanded, *out; int depth; } stbi__png; enum { STBI__F_none=0, STBI__F_sub=1, STBI__F_up=2, STBI__F_avg=3, STBI__F_paeth=4, // synthetic filters used for first scanline to avoid needing a dummy row of 0s STBI__F_avg_first, STBI__F_paeth_first }; static stbi_uc first_row_filter[5] = { STBI__F_none, STBI__F_sub, STBI__F_none, STBI__F_avg_first, STBI__F_paeth_first }; static int stbi__paeth(int a, int b, int c) { int p = a + b - c; int pa = abs(p-a); int pb = abs(p-b); int pc = abs(p-c); if (pa <= pb && pa <= pc) return a; if (pb <= pc) return b; return c; } static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; // create the png data from post-deflated data static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) { int bytes = (depth == 16? 2 : 1); stbi__context *s = a->s; stbi__uint32 i,j,stride = x*out_n*bytes; stbi__uint32 img_len, img_width_bytes; int k; int img_n = s->img_n; // copy it into a local for later int output_bytes = out_n*bytes; int filter_bytes = img_n*bytes; int width = x; STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); img_width_bytes = (((img_n * x * depth) + 7) >> 3); img_len = (img_width_bytes + 1) * y; // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), // so just check for raw_len < img_len always. if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); for (j=0; j < y; ++j) { stbi_uc *cur = a->out + stride*j; stbi_uc *prior; int filter = *raw++; if (filter > 4) return stbi__err("invalid filter","Corrupt PNG"); if (depth < 8) { STBI_ASSERT(img_width_bytes <= x); cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place filter_bytes = 1; width = img_width_bytes; } prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above // if first row, use special filter that doesn't sample previous row if (j == 0) filter = first_row_filter[filter]; // handle first byte explicitly for (k=0; k < filter_bytes; ++k) { switch (filter) { case STBI__F_none : cur[k] = raw[k]; break; case STBI__F_sub : cur[k] = raw[k]; break; case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; case STBI__F_avg_first : cur[k] = raw[k]; break; case STBI__F_paeth_first: cur[k] = raw[k]; break; } } if (depth == 8) { if (img_n != out_n) cur[img_n] = 255; // first pixel raw += img_n; cur += out_n; prior += out_n; } else if (depth == 16) { if (img_n != out_n) { cur[filter_bytes] = 255; // first pixel top byte cur[filter_bytes+1] = 255; // first pixel bottom byte } raw += filter_bytes; cur += output_bytes; prior += output_bytes; } else { raw += 1; cur += 1; prior += 1; } // this is a little gross, so that we don't switch per-pixel or per-component if (depth < 8 || img_n == out_n) { int nk = (width - 1)*filter_bytes; #define STBI__CASE(f) \ case f: \ for (k=0; k < nk; ++k) switch (filter) { // "none" filter turns into a memcpy here; make that explicit. case STBI__F_none: memcpy(cur, raw, nk); break; STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; } #undef STBI__CASE raw += nk; } else { STBI_ASSERT(img_n+1 == out_n); #define STBI__CASE(f) \ case f: \ for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ for (k=0; k < filter_bytes; ++k) switch (filter) { STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; } #undef STBI__CASE // the loop above sets the high byte of the pixels' alpha, but for // 16 bit png files we also need the low byte set. we'll do that here. if (depth == 16) { cur = a->out + stride*j; // start at the beginning of the row again for (i=0; i < x; ++i,cur+=output_bytes) { cur[filter_bytes+1] = 255; } } } } // we make a separate pass to expand bits to pixels; for performance, // this could run two scanlines behind the above code, so it won't // intefere with filtering but will still be in the cache. if (depth < 8) { for (j=0; j < y; ++j) { stbi_uc *cur = a->out + stride*j; stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range // note that the final byte might overshoot and write more data than desired. // we can allocate enough data that this never writes out of memory, but it // could also overwrite the next scanline. can it overwrite non-empty data // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. // so we need to explicitly clamp the final ones if (depth == 4) { for (k=x*img_n; k >= 2; k-=2, ++in) { *cur++ = scale * ((*in >> 4) ); *cur++ = scale * ((*in ) & 0x0f); } if (k > 0) *cur++ = scale * ((*in >> 4) ); } else if (depth == 2) { for (k=x*img_n; k >= 4; k-=4, ++in) { *cur++ = scale * ((*in >> 6) ); *cur++ = scale * ((*in >> 4) & 0x03); *cur++ = scale * ((*in >> 2) & 0x03); *cur++ = scale * ((*in ) & 0x03); } if (k > 0) *cur++ = scale * ((*in >> 6) ); if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); } else if (depth == 1) { for (k=x*img_n; k >= 8; k-=8, ++in) { *cur++ = scale * ((*in >> 7) ); *cur++ = scale * ((*in >> 6) & 0x01); *cur++ = scale * ((*in >> 5) & 0x01); *cur++ = scale * ((*in >> 4) & 0x01); *cur++ = scale * ((*in >> 3) & 0x01); *cur++ = scale * ((*in >> 2) & 0x01); *cur++ = scale * ((*in >> 1) & 0x01); *cur++ = scale * ((*in ) & 0x01); } if (k > 0) *cur++ = scale * ((*in >> 7) ); if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); } if (img_n != out_n) { int q; // insert alpha = 255 cur = a->out + stride*j; if (img_n == 1) { for (q=x-1; q >= 0; --q) { cur[q*2+1] = 255; cur[q*2+0] = cur[q]; } } else { STBI_ASSERT(img_n == 3); for (q=x-1; q >= 0; --q) { cur[q*4+3] = 255; cur[q*4+2] = cur[q*3+2]; cur[q*4+1] = cur[q*3+1]; cur[q*4+0] = cur[q*3+0]; } } } } } else if (depth == 16) { // force the image data from big-endian to platform-native. // this is done in a separate pass due to the decoding relying // on the data being untouched, but could probably be done // per-line during decode if care is taken. stbi_uc *cur = a->out; stbi__uint16 *cur16 = (stbi__uint16*)cur; for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { *cur16 = (cur[0] << 8) | cur[1]; } } return 1; } static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) { int bytes = (depth == 16 ? 2 : 1); int out_bytes = out_n * bytes; stbi_uc *final; int p; if (!interlaced) return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); // de-interlacing final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); for (p=0; p < 7; ++p) { int xorig[] = { 0,4,0,2,0,1,0 }; int yorig[] = { 0,0,4,0,2,0,1 }; int xspc[] = { 8,8,4,4,2,2,1 }; int yspc[] = { 8,8,8,4,4,2,2 }; int i,j,x,y; // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; if (x && y) { stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { STBI_FREE(final); return 0; } for (j=0; j < y; ++j) { for (i=0; i < x; ++i) { int out_y = j*yspc[p]+yorig[p]; int out_x = i*xspc[p]+xorig[p]; memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, a->out + (j*x+i)*out_bytes, out_bytes); } } STBI_FREE(a->out); image_data += img_len; image_data_len -= img_len; } } a->out = final; return 1; } static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) { stbi__context *s = z->s; stbi__uint32 i, pixel_count = s->img_x * s->img_y; stbi_uc *p = z->out; // compute color-based transparency, assuming we've // already got 255 as the alpha value in the output STBI_ASSERT(out_n == 2 || out_n == 4); if (out_n == 2) { for (i=0; i < pixel_count; ++i) { p[1] = (p[0] == tc[0] ? 0 : 255); p += 2; } } else { for (i=0; i < pixel_count; ++i) { if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) p[3] = 0; p += 4; } } return 1; } static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) { stbi__context *s = z->s; stbi__uint32 i, pixel_count = s->img_x * s->img_y; stbi__uint16 *p = (stbi__uint16*) z->out; // compute color-based transparency, assuming we've // already got 65535 as the alpha value in the output STBI_ASSERT(out_n == 2 || out_n == 4); if (out_n == 2) { for (i = 0; i < pixel_count; ++i) { p[1] = (p[0] == tc[0] ? 0 : 65535); p += 2; } } else { for (i = 0; i < pixel_count; ++i) { if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) p[3] = 0; p += 4; } } return 1; } static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) { stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; stbi_uc *p, *temp_out, *orig = a->out; p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); if (p == NULL) return stbi__err("outofmem", "Out of memory"); // between here and free(out) below, exitting would leak temp_out = p; if (pal_img_n == 3) { for (i=0; i < pixel_count; ++i) { int n = orig[i]*4; p[0] = palette[n ]; p[1] = palette[n+1]; p[2] = palette[n+2]; p += 3; } } else { for (i=0; i < pixel_count; ++i) { int n = orig[i]*4; p[0] = palette[n ]; p[1] = palette[n+1]; p[2] = palette[n+2]; p[3] = palette[n+3]; p += 4; } } STBI_FREE(a->out); a->out = temp_out; STBI_NOTUSED(len); return 1; } static int stbi__unpremultiply_on_load = 0; static int stbi__de_iphone_flag = 0; STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) { stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; } STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) { stbi__de_iphone_flag = flag_true_if_should_convert; } static void stbi__de_iphone(stbi__png *z) { stbi__context *s = z->s; stbi__uint32 i, pixel_count = s->img_x * s->img_y; stbi_uc *p = z->out; if (s->img_out_n == 3) { // convert bgr to rgb for (i=0; i < pixel_count; ++i) { stbi_uc t = p[0]; p[0] = p[2]; p[2] = t; p += 3; } } else { STBI_ASSERT(s->img_out_n == 4); if (stbi__unpremultiply_on_load) { // convert bgr to rgb and unpremultiply for (i=0; i < pixel_count; ++i) { stbi_uc a = p[3]; stbi_uc t = p[0]; if (a) { stbi_uc half = a / 2; p[0] = (p[2] * 255 + half) / a; p[1] = (p[1] * 255 + half) / a; p[2] = ( t * 255 + half) / a; } else { p[0] = p[2]; p[2] = t; } p += 4; } } else { // convert bgr to rgb for (i=0; i < pixel_count; ++i) { stbi_uc t = p[0]; p[0] = p[2]; p[2] = t; p += 4; } } } } #define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) { stbi_uc palette[1024], pal_img_n=0; stbi_uc has_trans=0, tc[3]; stbi__uint16 tc16[3]; stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; int first=1,k,interlace=0, color=0, is_iphone=0; stbi__context *s = z->s; z->expanded = NULL; z->idata = NULL; z->out = NULL; if (!stbi__check_png_header(s)) return 0; if (scan == STBI__SCAN_type) return 1; for (;;) { stbi__pngchunk c = stbi__get_chunk_header(s); switch (c.type) { case STBI__PNG_TYPE('C','g','B','I'): is_iphone = 1; stbi__skip(s, c.length); break; case STBI__PNG_TYPE('I','H','D','R'): { int comp,filter; if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); first = 0; if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); if (!pal_img_n) { s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); if (scan == STBI__SCAN_header) return 1; } else { // if paletted, then pal_n is our final components, and // img_n is # components to decompress/filter. s->img_n = 1; if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); // if SCAN_header, have to scan to see if we have a tRNS } break; } case STBI__PNG_TYPE('P','L','T','E'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); pal_len = c.length / 3; if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); for (i=0; i < pal_len; ++i) { palette[i*4+0] = stbi__get8(s); palette[i*4+1] = stbi__get8(s); palette[i*4+2] = stbi__get8(s); palette[i*4+3] = 255; } break; } case STBI__PNG_TYPE('t','R','N','S'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); if (pal_img_n) { if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); pal_img_n = 4; for (i=0; i < c.length; ++i) palette[i*4+3] = stbi__get8(s); } else { if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); has_trans = 1; if (z->depth == 16) { for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is } else { for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger } } break; } case STBI__PNG_TYPE('I','D','A','T'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } if ((int)(ioff + c.length) < (int)ioff) return 0; if (ioff + c.length > idata_limit) { stbi__uint32 idata_limit_old = idata_limit; stbi_uc *p; if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; while (ioff + c.length > idata_limit) idata_limit *= 2; STBI_NOTUSED(idata_limit_old); p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); z->idata = p; } if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); ioff += c.length; break; } case STBI__PNG_TYPE('I','E','N','D'): { stbi__uint32 raw_len, bpl; if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (scan != STBI__SCAN_load) return 1; if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); // initial guess for decoded data size to avoid unnecessary reallocs bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); if (z->expanded == NULL) return 0; // zlib should set error STBI_FREE(z->idata); z->idata = NULL; if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) s->img_out_n = s->img_n+1; else s->img_out_n = s->img_n; if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; if (has_trans) { if (z->depth == 16) { if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; } else { if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; } } if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) stbi__de_iphone(z); if (pal_img_n) { // pal_img_n == 3 or 4 s->img_n = pal_img_n; // record the actual colors we had s->img_out_n = pal_img_n; if (req_comp >= 3) s->img_out_n = req_comp; if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) return 0; } else if (has_trans) { // non-paletted image with tRNS -> source image has (constant) alpha ++s->img_n; } STBI_FREE(z->expanded); z->expanded = NULL; return 1; } default: // if critical, fail if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if ((c.type & (1 << 29)) == 0) { #ifndef STBI_NO_FAILURE_STRINGS // not threadsafe static char invalid_chunk[] = "XXXX PNG chunk not known"; invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); #endif return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); } stbi__skip(s, c.length); break; } // end of PNG chunk, read and skip CRC stbi__get32be(s); } } static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) { void *result=NULL; if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { if (p->depth < 8) ri->bits_per_channel = 8; else ri->bits_per_channel = p->depth; result = p->out; p->out = NULL; if (req_comp && req_comp != p->s->img_out_n) { if (ri->bits_per_channel == 8) result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); else result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); p->s->img_out_n = req_comp; if (result == NULL) return result; } *x = p->s->img_x; *y = p->s->img_y; if (n) *n = p->s->img_n; } STBI_FREE(p->out); p->out = NULL; STBI_FREE(p->expanded); p->expanded = NULL; STBI_FREE(p->idata); p->idata = NULL; return result; } static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { stbi__png p; p.s = s; return stbi__do_png(&p, x,y,comp,req_comp, ri); } static int stbi__png_test(stbi__context *s) { int r; r = stbi__check_png_header(s); stbi__rewind(s); return r; } static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) { if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { stbi__rewind( p->s ); return 0; } if (x) *x = p->s->img_x; if (y) *y = p->s->img_y; if (comp) *comp = p->s->img_n; return 1; } static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) { stbi__png p; p.s = s; return stbi__png_info_raw(&p, x, y, comp); } #endif // Microsoft/Windows BMP image #ifndef STBI_NO_BMP static int stbi__bmp_test_raw(stbi__context *s) { int r; int sz; if (stbi__get8(s) != 'B') return 0; if (stbi__get8(s) != 'M') return 0; stbi__get32le(s); // discard filesize stbi__get16le(s); // discard reserved stbi__get16le(s); // discard reserved stbi__get32le(s); // discard data offset sz = stbi__get32le(s); r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); return r; } static int stbi__bmp_test(stbi__context *s) { int r = stbi__bmp_test_raw(s); stbi__rewind(s); return r; } // returns 0..31 for the highest set bit static int stbi__high_bit(unsigned int z) { int n=0; if (z == 0) return -1; if (z >= 0x10000) n += 16, z >>= 16; if (z >= 0x00100) n += 8, z >>= 8; if (z >= 0x00010) n += 4, z >>= 4; if (z >= 0x00004) n += 2, z >>= 2; if (z >= 0x00002) n += 1, z >>= 1; return n; } static int stbi__bitcount(unsigned int a) { a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits a = (a + (a >> 8)); // max 16 per 8 bits a = (a + (a >> 16)); // max 32 per 8 bits return a & 0xff; } static int stbi__shiftsigned(int v, int shift, int bits) { int result; int z=0; if (shift < 0) v <<= -shift; else v >>= shift; result = v; z = bits; while (z < 8) { result += v >> z; z += bits; } return result; } typedef struct { int bpp, offset, hsz; unsigned int mr,mg,mb,ma, all_a; } stbi__bmp_data; static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) { int hsz; if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); stbi__get32le(s); // discard filesize stbi__get16le(s); // discard reserved stbi__get16le(s); // discard reserved info->offset = stbi__get32le(s); info->hsz = hsz = stbi__get32le(s); info->mr = info->mg = info->mb = info->ma = 0; if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); if (hsz == 12) { s->img_x = stbi__get16le(s); s->img_y = stbi__get16le(s); } else { s->img_x = stbi__get32le(s); s->img_y = stbi__get32le(s); } if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); info->bpp = stbi__get16le(s); if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); if (hsz != 12) { int compress = stbi__get32le(s); if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); stbi__get32le(s); // discard sizeof stbi__get32le(s); // discard hres stbi__get32le(s); // discard vres stbi__get32le(s); // discard colorsused stbi__get32le(s); // discard max important if (hsz == 40 || hsz == 56) { if (hsz == 56) { stbi__get32le(s); stbi__get32le(s); stbi__get32le(s); stbi__get32le(s); } if (info->bpp == 16 || info->bpp == 32) { if (compress == 0) { if (info->bpp == 32) { info->mr = 0xffu << 16; info->mg = 0xffu << 8; info->mb = 0xffu << 0; info->ma = 0xffu << 24; info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 } else { info->mr = 31u << 10; info->mg = 31u << 5; info->mb = 31u << 0; } } else if (compress == 3) { info->mr = stbi__get32le(s); info->mg = stbi__get32le(s); info->mb = stbi__get32le(s); // not documented, but generated by photoshop and handled by mspaint if (info->mr == info->mg && info->mg == info->mb) { // ?!?!? return stbi__errpuc("bad BMP", "bad BMP"); } } else return stbi__errpuc("bad BMP", "bad BMP"); } } else { int i; if (hsz != 108 && hsz != 124) return stbi__errpuc("bad BMP", "bad BMP"); info->mr = stbi__get32le(s); info->mg = stbi__get32le(s); info->mb = stbi__get32le(s); info->ma = stbi__get32le(s); stbi__get32le(s); // discard color space for (i=0; i < 12; ++i) stbi__get32le(s); // discard color space parameters if (hsz == 124) { stbi__get32le(s); // discard rendering intent stbi__get32le(s); // discard offset of profile data stbi__get32le(s); // discard size of profile data stbi__get32le(s); // discard reserved } } } return (void *) 1; } static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { stbi_uc *out; unsigned int mr=0,mg=0,mb=0,ma=0, all_a; stbi_uc pal[256][4]; int psize=0,i,j,width; int flip_vertically, pad, target; stbi__bmp_data info; STBI_NOTUSED(ri); info.all_a = 255; if (stbi__bmp_parse_header(s, &info) == NULL) return NULL; // error code already set flip_vertically = ((int) s->img_y) > 0; s->img_y = abs((int) s->img_y); mr = info.mr; mg = info.mg; mb = info.mb; ma = info.ma; all_a = info.all_a; if (info.hsz == 12) { if (info.bpp < 24) psize = (info.offset - 14 - 24) / 3; } else { if (info.bpp < 16) psize = (info.offset - 14 - info.hsz) >> 2; } s->img_n = ma ? 4 : 3; if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 target = req_comp; else target = s->img_n; // if they want monochrome, we'll post-convert // sanity-check size if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) return stbi__errpuc("too large", "Corrupt BMP"); out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); if (!out) return stbi__errpuc("outofmem", "Out of memory"); if (info.bpp < 16) { int z=0; if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } for (i=0; i < psize; ++i) { pal[i][2] = stbi__get8(s); pal[i][1] = stbi__get8(s); pal[i][0] = stbi__get8(s); if (info.hsz != 12) stbi__get8(s); pal[i][3] = 255; } stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); if (info.bpp == 4) width = (s->img_x + 1) >> 1; else if (info.bpp == 8) width = s->img_x; else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } pad = (-width)&3; for (j=0; j < (int) s->img_y; ++j) { for (i=0; i < (int) s->img_x; i += 2) { int v=stbi__get8(s),v2=0; if (info.bpp == 4) { v2 = v & 15; v >>= 4; } out[z++] = pal[v][0]; out[z++] = pal[v][1]; out[z++] = pal[v][2]; if (target == 4) out[z++] = 255; if (i+1 == (int) s->img_x) break; v = (info.bpp == 8) ? stbi__get8(s) : v2; out[z++] = pal[v][0]; out[z++] = pal[v][1]; out[z++] = pal[v][2]; if (target == 4) out[z++] = 255; } stbi__skip(s, pad); } } else { int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; int z = 0; int easy=0; stbi__skip(s, info.offset - 14 - info.hsz); if (info.bpp == 24) width = 3 * s->img_x; else if (info.bpp == 16) width = 2*s->img_x; else /* bpp = 32 and pad = 0 */ width=0; pad = (-width) & 3; if (info.bpp == 24) { easy = 1; } else if (info.bpp == 32) { if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) easy = 2; } if (!easy) { if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } // right shift amt to put high bit in position #7 rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); } for (j=0; j < (int) s->img_y; ++j) { if (easy) { for (i=0; i < (int) s->img_x; ++i) { unsigned char a; out[z+2] = stbi__get8(s); out[z+1] = stbi__get8(s); out[z+0] = stbi__get8(s); z += 3; a = (easy == 2 ? stbi__get8(s) : 255); all_a |= a; if (target == 4) out[z++] = a; } } else { int bpp = info.bpp; for (i=0; i < (int) s->img_x; ++i) { stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); int a; out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); all_a |= a; if (target == 4) out[z++] = STBI__BYTECAST(a); } } stbi__skip(s, pad); } } // if alpha channel is all 0s, replace with all 255s if (target == 4 && all_a == 0) for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) out[i] = 255; if (flip_vertically) { stbi_uc t; for (j=0; j < (int) s->img_y>>1; ++j) { stbi_uc *p1 = out + j *s->img_x*target; stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; for (i=0; i < (int) s->img_x*target; ++i) { t = p1[i], p1[i] = p2[i], p2[i] = t; } } } if (req_comp && req_comp != target) { out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); if (out == NULL) return out; // stbi__convert_format frees input on failure } *x = s->img_x; *y = s->img_y; if (comp) *comp = s->img_n; return out; } #endif // Targa Truevision - TGA // by Jonathan Dummer #ifndef STBI_NO_TGA // returns STBI_rgb or whatever, 0 on error static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) { // only RGB or RGBA (incl. 16bit) or grey allowed if(is_rgb16) *is_rgb16 = 0; switch(bits_per_pixel) { case 8: return STBI_grey; case 16: if(is_grey) return STBI_grey_alpha; // else: fall-through case 15: if(is_rgb16) *is_rgb16 = 1; return STBI_rgb; case 24: // fall-through case 32: return bits_per_pixel/8; default: return 0; } } static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) { int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; int sz, tga_colormap_type; stbi__get8(s); // discard Offset tga_colormap_type = stbi__get8(s); // colormap type if( tga_colormap_type > 1 ) { stbi__rewind(s); return 0; // only RGB or indexed allowed } tga_image_type = stbi__get8(s); // image type if ( tga_colormap_type == 1 ) { // colormapped (paletted) image if (tga_image_type != 1 && tga_image_type != 9) { stbi__rewind(s); return 0; } stbi__skip(s,4); // skip index of first colormap entry and number of entries sz = stbi__get8(s); // check bits per palette color entry if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { stbi__rewind(s); return 0; } stbi__skip(s,4); // skip image x and y origin tga_colormap_bpp = sz; } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { stbi__rewind(s); return 0; // only RGB or grey allowed, +/- RLE } stbi__skip(s,9); // skip colormap specification and image x/y origin tga_colormap_bpp = 0; } tga_w = stbi__get16le(s); if( tga_w < 1 ) { stbi__rewind(s); return 0; // test width } tga_h = stbi__get16le(s); if( tga_h < 1 ) { stbi__rewind(s); return 0; // test height } tga_bits_per_pixel = stbi__get8(s); // bits per pixel stbi__get8(s); // ignore alpha bits if (tga_colormap_bpp != 0) { if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { // when using a colormap, tga_bits_per_pixel is the size of the indexes // I don't think anything but 8 or 16bit indexes makes sense stbi__rewind(s); return 0; } tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); } else { tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); } if(!tga_comp) { stbi__rewind(s); return 0; } if (x) *x = tga_w; if (y) *y = tga_h; if (comp) *comp = tga_comp; return 1; // seems to have passed everything } static int stbi__tga_test(stbi__context *s) { int res = 0; int sz, tga_color_type; stbi__get8(s); // discard Offset tga_color_type = stbi__get8(s); // color type if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed sz = stbi__get8(s); // image type if ( tga_color_type == 1 ) { // colormapped (paletted) image if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 stbi__skip(s,4); // skip index of first colormap entry and number of entries sz = stbi__get8(s); // check bits per palette color entry if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; stbi__skip(s,4); // skip image x and y origin } else { // "normal" image w/o colormap if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE stbi__skip(s,9); // skip colormap specification and image x/y origin } if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height sz = stbi__get8(s); // bits per pixel if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; res = 1; // if we got this far, everything's good and we can return 1 instead of 0 errorEnd: stbi__rewind(s); return res; } // read 16bit value and convert to 24bit RGB static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) { stbi__uint16 px = (stbi__uint16)stbi__get16le(s); stbi__uint16 fiveBitMask = 31; // we have 3 channels with 5bits each int r = (px >> 10) & fiveBitMask; int g = (px >> 5) & fiveBitMask; int b = px & fiveBitMask; // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later out[0] = (stbi_uc)((r * 255)/31); out[1] = (stbi_uc)((g * 255)/31); out[2] = (stbi_uc)((b * 255)/31); // some people claim that the most significant bit might be used for alpha // (possibly if an alpha-bit is set in the "image descriptor byte") // but that only made 16bit test images completely translucent.. // so let's treat all 15 and 16bit TGAs as RGB with no alpha. } static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { // read in the TGA header stuff int tga_offset = stbi__get8(s); int tga_indexed = stbi__get8(s); int tga_image_type = stbi__get8(s); int tga_is_RLE = 0; int tga_palette_start = stbi__get16le(s); int tga_palette_len = stbi__get16le(s); int tga_palette_bits = stbi__get8(s); int tga_x_origin = stbi__get16le(s); int tga_y_origin = stbi__get16le(s); int tga_width = stbi__get16le(s); int tga_height = stbi__get16le(s); int tga_bits_per_pixel = stbi__get8(s); int tga_comp, tga_rgb16=0; int tga_inverted = stbi__get8(s); // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) // image data unsigned char *tga_data; unsigned char *tga_palette = NULL; int i, j; unsigned char raw_data[4] = {0}; int RLE_count = 0; int RLE_repeating = 0; int read_next_pixel = 1; STBI_NOTUSED(ri); // do a tiny bit of precessing if ( tga_image_type >= 8 ) { tga_image_type -= 8; tga_is_RLE = 1; } tga_inverted = 1 - ((tga_inverted >> 5) & 1); // If I'm paletted, then I'll use the number of bits from the palette if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); // tga info *x = tga_width; *y = tga_height; if (comp) *comp = tga_comp; if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) return stbi__errpuc("too large", "Corrupt TGA"); tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); // skip to the data's starting position (offset usually = 0) stbi__skip(s, tga_offset ); if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { for (i=0; i < tga_height; ++i) { int row = tga_inverted ? tga_height -i - 1 : i; stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; stbi__getn(s, tga_row, tga_width * tga_comp); } } else { // do I need to load a palette? if ( tga_indexed) { // any data to skip? (offset usually = 0) stbi__skip(s, tga_palette_start ); // load the palette tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); if (!tga_palette) { STBI_FREE(tga_data); return stbi__errpuc("outofmem", "Out of memory"); } if (tga_rgb16) { stbi_uc *pal_entry = tga_palette; STBI_ASSERT(tga_comp == STBI_rgb); for (i=0; i < tga_palette_len; ++i) { stbi__tga_read_rgb16(s, pal_entry); pal_entry += tga_comp; } } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { STBI_FREE(tga_data); STBI_FREE(tga_palette); return stbi__errpuc("bad palette", "Corrupt TGA"); } } // load the data for (i=0; i < tga_width * tga_height; ++i) { // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? if ( tga_is_RLE ) { if ( RLE_count == 0 ) { // yep, get the next byte as a RLE command int RLE_cmd = stbi__get8(s); RLE_count = 1 + (RLE_cmd & 127); RLE_repeating = RLE_cmd >> 7; read_next_pixel = 1; } else if ( !RLE_repeating ) { read_next_pixel = 1; } } else { read_next_pixel = 1; } // OK, if I need to read a pixel, do it now if ( read_next_pixel ) { // load however much data we did have if ( tga_indexed ) { // read in index, then perform the lookup int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); if ( pal_idx >= tga_palette_len ) { // invalid index pal_idx = 0; } pal_idx *= tga_comp; for (j = 0; j < tga_comp; ++j) { raw_data[j] = tga_palette[pal_idx+j]; } } else if(tga_rgb16) { STBI_ASSERT(tga_comp == STBI_rgb); stbi__tga_read_rgb16(s, raw_data); } else { // read in the data raw for (j = 0; j < tga_comp; ++j) { raw_data[j] = stbi__get8(s); } } // clear the reading flag for the next pixel read_next_pixel = 0; } // end of reading a pixel // copy data for (j = 0; j < tga_comp; ++j) tga_data[i*tga_comp+j] = raw_data[j]; // in case we're in RLE mode, keep counting down --RLE_count; } // do I need to invert the image? if ( tga_inverted ) { for (j = 0; j*2 < tga_height; ++j) { int index1 = j * tga_width * tga_comp; int index2 = (tga_height - 1 - j) * tga_width * tga_comp; for (i = tga_width * tga_comp; i > 0; --i) { unsigned char temp = tga_data[index1]; tga_data[index1] = tga_data[index2]; tga_data[index2] = temp; ++index1; ++index2; } } } // clear my palette, if I had one if ( tga_palette != NULL ) { STBI_FREE( tga_palette ); } } // swap RGB - if the source data was RGB16, it already is in the right order if (tga_comp >= 3 && !tga_rgb16) { unsigned char* tga_pixel = tga_data; for (i=0; i < tga_width * tga_height; ++i) { unsigned char temp = tga_pixel[0]; tga_pixel[0] = tga_pixel[2]; tga_pixel[2] = temp; tga_pixel += tga_comp; } } // convert to target component count if (req_comp && req_comp != tga_comp) tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); // the things I do to get rid of an error message, and yet keep // Microsoft's C compilers happy... [8^( tga_palette_start = tga_palette_len = tga_palette_bits = tga_x_origin = tga_y_origin = 0; // OK, done return tga_data; } #endif // ************************************************************************************************* // Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB #ifndef STBI_NO_PSD static int stbi__psd_test(stbi__context *s) { int r = (stbi__get32be(s) == 0x38425053); stbi__rewind(s); return r; } static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) { int count, nleft, len; count = 0; while ((nleft = pixelCount - count) > 0) { len = stbi__get8(s); if (len == 128) { // No-op. } else if (len < 128) { // Copy next len+1 bytes literally. len++; if (len > nleft) return 0; // corrupt data count += len; while (len) { *p = stbi__get8(s); p += 4; len--; } } else if (len > 128) { stbi_uc val; // Next -len+1 bytes in the dest are replicated from next source byte. // (Interpret len as a negative 8-bit int.) len = 257 - len; if (len > nleft) return 0; // corrupt data val = stbi__get8(s); count += len; while (len) { *p = val; p += 4; len--; } } } return 1; } static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) { int pixelCount; int channelCount, compression; int channel, i; int bitdepth; int w,h; stbi_uc *out; STBI_NOTUSED(ri); // Check identifier if (stbi__get32be(s) != 0x38425053) // "8BPS" return stbi__errpuc("not PSD", "Corrupt PSD image"); // Check file type version. if (stbi__get16be(s) != 1) return stbi__errpuc("wrong version", "Unsupported version of PSD image"); // Skip 6 reserved bytes. stbi__skip(s, 6 ); // Read the number of channels (R, G, B, A, etc). channelCount = stbi__get16be(s); if (channelCount < 0 || channelCount > 16) return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); // Read the rows and columns of the image. h = stbi__get32be(s); w = stbi__get32be(s); // Make sure the depth is 8 bits. bitdepth = stbi__get16be(s); if (bitdepth != 8 && bitdepth != 16) return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); // Make sure the color mode is RGB. // Valid options are: // 0: Bitmap // 1: Grayscale // 2: Indexed color // 3: RGB color // 4: CMYK color // 7: Multichannel // 8: Duotone // 9: Lab color if (stbi__get16be(s) != 3) return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) stbi__skip(s,stbi__get32be(s) ); // Skip the image resources. (resolution, pen tool paths, etc) stbi__skip(s, stbi__get32be(s) ); // Skip the reserved data. stbi__skip(s, stbi__get32be(s) ); // Find out if the data is compressed. // Known values: // 0: no compression // 1: RLE compressed compression = stbi__get16be(s); if (compression > 1) return stbi__errpuc("bad compression", "PSD has an unknown compression format"); // Check size if (!stbi__mad3sizes_valid(4, w, h, 0)) return stbi__errpuc("too large", "Corrupt PSD"); // Create the destination image. if (!compression && bitdepth == 16 && bpc == 16) { out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); ri->bits_per_channel = 16; } else out = (stbi_uc *) stbi__malloc(4 * w*h); if (!out) return stbi__errpuc("outofmem", "Out of memory"); pixelCount = w*h; // Initialize the data to zero. //memset( out, 0, pixelCount * 4 ); // Finally, the image data. if (compression) { // RLE as used by .PSD and .TIFF // Loop until you get the number of unpacked bytes you are expecting: // Read the next source byte into n. // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. // Else if n is 128, noop. // Endloop // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, // which we're going to just skip. stbi__skip(s, h * channelCount * 2 ); // Read the RLE data by channel. for (channel = 0; channel < 4; channel++) { stbi_uc *p; p = out+channel; if (channel >= channelCount) { // Fill this channel with default data. for (i = 0; i < pixelCount; i++, p += 4) *p = (channel == 3 ? 255 : 0); } else { // Read the RLE data. if (!stbi__psd_decode_rle(s, p, pixelCount)) { STBI_FREE(out); return stbi__errpuc("corrupt", "bad RLE data"); } } } } else { // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. // Read the data by channel. for (channel = 0; channel < 4; channel++) { if (channel >= channelCount) { // Fill this channel with default data. if (bitdepth == 16 && bpc == 16) { stbi__uint16 *q = ((stbi__uint16 *) out) + channel; stbi__uint16 val = channel == 3 ? 65535 : 0; for (i = 0; i < pixelCount; i++, q += 4) *q = val; } else { stbi_uc *p = out+channel; stbi_uc val = channel == 3 ? 255 : 0; for (i = 0; i < pixelCount; i++, p += 4) *p = val; } } else { if (ri->bits_per_channel == 16) { // output bpc stbi__uint16 *q = ((stbi__uint16 *) out) + channel; for (i = 0; i < pixelCount; i++, q += 4) *q = (stbi__uint16) stbi__get16be(s); } else { stbi_uc *p = out+channel; if (bitdepth == 16) { // input bpc for (i = 0; i < pixelCount; i++, p += 4) *p = (stbi_uc) (stbi__get16be(s) >> 8); } else { for (i = 0; i < pixelCount; i++, p += 4) *p = stbi__get8(s); } } } } } // remove weird white matte from PSD if (channelCount >= 4) { if (ri->bits_per_channel == 16) { for (i=0; i < w*h; ++i) { stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; if (pixel[3] != 0 && pixel[3] != 65535) { float a = pixel[3] / 65535.0f; float ra = 1.0f / a; float inv_a = 65535.0f * (1 - ra); pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); } } } else { for (i=0; i < w*h; ++i) { unsigned char *pixel = out + 4*i; if (pixel[3] != 0 && pixel[3] != 255) { float a = pixel[3] / 255.0f; float ra = 1.0f / a; float inv_a = 255.0f * (1 - ra); pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); } } } } // convert to desired output format if (req_comp && req_comp != 4) { if (ri->bits_per_channel == 16) out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); else out = stbi__convert_format(out, 4, req_comp, w, h); if (out == NULL) return out; // stbi__convert_format frees input on failure } if (comp) *comp = 4; *y = h; *x = w; return out; } #endif // ************************************************************************************************* // Softimage PIC loader // by Tom Seddon // // See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format // See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ #ifndef STBI_NO_PIC static int stbi__pic_is4(stbi__context *s,const char *str) { int i; for (i=0; i<4; ++i) if (stbi__get8(s) != (stbi_uc)str[i]) return 0; return 1; } static int stbi__pic_test_core(stbi__context *s) { int i; if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) return 0; for(i=0;i<84;++i) stbi__get8(s); if (!stbi__pic_is4(s,"PICT")) return 0; return 1; } typedef struct { stbi_uc size,type,channel; } stbi__pic_packet; static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) { int mask=0x80, i; for (i=0; i<4; ++i, mask>>=1) { if (channel & mask) { if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); dest[i]=stbi__get8(s); } } return dest; } static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) { int mask=0x80,i; for (i=0;i<4; ++i, mask>>=1) if (channel&mask) dest[i]=src[i]; } static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) { int act_comp=0,num_packets=0,y,chained; stbi__pic_packet packets[10]; // this will (should...) cater for even some bizarre stuff like having data // for the same channel in multiple packets. do { stbi__pic_packet *packet; if (num_packets==sizeof(packets)/sizeof(packets[0])) return stbi__errpuc("bad format","too many packets"); packet = &packets[num_packets++]; chained = stbi__get8(s); packet->size = stbi__get8(s); packet->type = stbi__get8(s); packet->channel = stbi__get8(s); act_comp |= packet->channel; if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); } while (chained); *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? for(y=0; ytype) { default: return stbi__errpuc("bad format","packet has bad compression type"); case 0: {//uncompressed int x; for(x=0;xchannel,dest)) return 0; break; } case 1://Pure RLE { int left=width, i; while (left>0) { stbi_uc count,value[4]; count=stbi__get8(s); if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); if (count > left) count = (stbi_uc) left; if (!stbi__readval(s,packet->channel,value)) return 0; for(i=0; ichannel,dest,value); left -= count; } } break; case 2: {//Mixed RLE int left=width; while (left>0) { int count = stbi__get8(s), i; if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); if (count >= 128) { // Repeated stbi_uc value[4]; if (count==128) count = stbi__get16be(s); else count -= 127; if (count > left) return stbi__errpuc("bad file","scanline overrun"); if (!stbi__readval(s,packet->channel,value)) return 0; for(i=0;ichannel,dest,value); } else { // Raw ++count; if (count>left) return stbi__errpuc("bad file","scanline overrun"); for(i=0;ichannel,dest)) return 0; } left-=count; } break; } } } } return result; } static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) { stbi_uc *result; int i, x,y, internal_comp; STBI_NOTUSED(ri); if (!comp) comp = &internal_comp; for (i=0; i<92; ++i) stbi__get8(s); x = stbi__get16be(s); y = stbi__get16be(s); if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); stbi__get32be(s); //skip `ratio' stbi__get16be(s); //skip `fields' stbi__get16be(s); //skip `pad' // intermediate buffer is RGBA result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); memset(result, 0xff, x*y*4); if (!stbi__pic_load_core(s,x,y,comp, result)) { STBI_FREE(result); result=0; } *px = x; *py = y; if (req_comp == 0) req_comp = *comp; result=stbi__convert_format(result,4,req_comp,x,y); return result; } static int stbi__pic_test(stbi__context *s) { int r = stbi__pic_test_core(s); stbi__rewind(s); return r; } #endif // ************************************************************************************************* // GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb #ifndef STBI_NO_GIF typedef struct { stbi__int16 prefix; stbi_uc first; stbi_uc suffix; } stbi__gif_lzw; typedef struct { int w,h; stbi_uc *out, *old_out; // output buffer (always 4 components) int flags, bgindex, ratio, transparent, eflags, delay; stbi_uc pal[256][4]; stbi_uc lpal[256][4]; stbi__gif_lzw codes[4096]; stbi_uc *color_table; int parse, step; int lflags; int start_x, start_y; int max_x, max_y; int cur_x, cur_y; int line_size; } stbi__gif; static int stbi__gif_test_raw(stbi__context *s) { int sz; if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; sz = stbi__get8(s); if (sz != '9' && sz != '7') return 0; if (stbi__get8(s) != 'a') return 0; return 1; } static int stbi__gif_test(stbi__context *s) { int r = stbi__gif_test_raw(s); stbi__rewind(s); return r; } static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) { int i; for (i=0; i < num_entries; ++i) { pal[i][2] = stbi__get8(s); pal[i][1] = stbi__get8(s); pal[i][0] = stbi__get8(s); pal[i][3] = transp == i ? 0 : 255; } } static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) { stbi_uc version; if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return stbi__err("not GIF", "Corrupt GIF"); version = stbi__get8(s); if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); stbi__g_failure_reason = ""; g->w = stbi__get16le(s); g->h = stbi__get16le(s); g->flags = stbi__get8(s); g->bgindex = stbi__get8(s); g->ratio = stbi__get8(s); g->transparent = -1; if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments if (is_info) return 1; if (g->flags & 0x80) stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); return 1; } static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) { stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); if (!stbi__gif_header(s, g, comp, 1)) { STBI_FREE(g); stbi__rewind( s ); return 0; } if (x) *x = g->w; if (y) *y = g->h; STBI_FREE(g); return 1; } static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) { stbi_uc *p, *c; // recurse to decode the prefixes, since the linked-list is backwards, // and working backwards through an interleaved image would be nasty if (g->codes[code].prefix >= 0) stbi__out_gif_code(g, g->codes[code].prefix); if (g->cur_y >= g->max_y) return; p = &g->out[g->cur_x + g->cur_y]; c = &g->color_table[g->codes[code].suffix * 4]; if (c[3] >= 128) { p[0] = c[2]; p[1] = c[1]; p[2] = c[0]; p[3] = c[3]; } g->cur_x += 4; if (g->cur_x >= g->max_x) { g->cur_x = g->start_x; g->cur_y += g->step; while (g->cur_y >= g->max_y && g->parse > 0) { g->step = (1 << g->parse) * g->line_size; g->cur_y = g->start_y + (g->step >> 1); --g->parse; } } } static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) { stbi_uc lzw_cs; stbi__int32 len, init_code; stbi__uint32 first; stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; stbi__gif_lzw *p; lzw_cs = stbi__get8(s); if (lzw_cs > 12) return NULL; clear = 1 << lzw_cs; first = 1; codesize = lzw_cs + 1; codemask = (1 << codesize) - 1; bits = 0; valid_bits = 0; for (init_code = 0; init_code < clear; init_code++) { g->codes[init_code].prefix = -1; g->codes[init_code].first = (stbi_uc) init_code; g->codes[init_code].suffix = (stbi_uc) init_code; } // support no starting clear code avail = clear+2; oldcode = -1; len = 0; for(;;) { if (valid_bits < codesize) { if (len == 0) { len = stbi__get8(s); // start new block if (len == 0) return g->out; } --len; bits |= (stbi__int32) stbi__get8(s) << valid_bits; valid_bits += 8; } else { stbi__int32 code = bits & codemask; bits >>= codesize; valid_bits -= codesize; // @OPTIMIZE: is there some way we can accelerate the non-clear path? if (code == clear) { // clear code codesize = lzw_cs + 1; codemask = (1 << codesize) - 1; avail = clear + 2; oldcode = -1; first = 0; } else if (code == clear + 1) { // end of stream code stbi__skip(s, len); while ((len = stbi__get8(s)) > 0) stbi__skip(s,len); return g->out; } else if (code <= avail) { if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); if (oldcode >= 0) { p = &g->codes[avail++]; if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); p->prefix = (stbi__int16) oldcode; p->first = g->codes[oldcode].first; p->suffix = (code == avail) ? p->first : g->codes[code].first; } else if (code == avail) return stbi__errpuc("illegal code in raster", "Corrupt GIF"); stbi__out_gif_code(g, (stbi__uint16) code); if ((avail & codemask) == 0 && avail <= 0x0FFF) { codesize++; codemask = (1 << codesize) - 1; } oldcode = code; } else { return stbi__errpuc("illegal code in raster", "Corrupt GIF"); } } } } static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) { int x, y; stbi_uc *c = g->pal[g->bgindex]; for (y = y0; y < y1; y += 4 * g->w) { for (x = x0; x < x1; x += 4) { stbi_uc *p = &g->out[y + x]; p[0] = c[2]; p[1] = c[1]; p[2] = c[0]; p[3] = 0; } } } // this function is designed to support animated gifs, although stb_image doesn't support it static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) { int i; stbi_uc *prev_out = 0; if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header if (!stbi__mad3sizes_valid(g->w, g->h, 4, 0)) return stbi__errpuc("too large", "GIF too large"); prev_out = g->out; g->out = (stbi_uc *) stbi__malloc_mad3(4, g->w, g->h, 0); if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); switch ((g->eflags & 0x1C) >> 2) { case 0: // unspecified (also always used on 1st frame) stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); break; case 1: // do not dispose if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); g->old_out = prev_out; break; case 2: // dispose to background if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); break; case 3: // dispose to previous if (g->old_out) { for (i = g->start_y; i < g->max_y; i += 4 * g->w) memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); } break; } for (;;) { switch (stbi__get8(s)) { case 0x2C: /* Image Descriptor */ { int prev_trans = -1; stbi__int32 x, y, w, h; stbi_uc *o; x = stbi__get16le(s); y = stbi__get16le(s); w = stbi__get16le(s); h = stbi__get16le(s); if (((x + w) > (g->w)) || ((y + h) > (g->h))) return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); g->line_size = g->w * 4; g->start_x = x * 4; g->start_y = y * g->line_size; g->max_x = g->start_x + w * 4; g->max_y = g->start_y + h * g->line_size; g->cur_x = g->start_x; g->cur_y = g->start_y; g->lflags = stbi__get8(s); if (g->lflags & 0x40) { g->step = 8 * g->line_size; // first interlaced spacing g->parse = 3; } else { g->step = g->line_size; g->parse = 0; } if (g->lflags & 0x80) { stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); g->color_table = (stbi_uc *) g->lpal; } else if (g->flags & 0x80) { if (g->transparent >= 0 && (g->eflags & 0x01)) { prev_trans = g->pal[g->transparent][3]; g->pal[g->transparent][3] = 0; } g->color_table = (stbi_uc *) g->pal; } else return stbi__errpuc("missing color table", "Corrupt GIF"); o = stbi__process_gif_raster(s, g); if (o == NULL) return NULL; if (prev_trans != -1) g->pal[g->transparent][3] = (stbi_uc) prev_trans; return o; } case 0x21: // Comment Extension. { int len; if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. len = stbi__get8(s); if (len == 4) { g->eflags = stbi__get8(s); g->delay = stbi__get16le(s); g->transparent = stbi__get8(s); } else { stbi__skip(s, len); break; } } while ((len = stbi__get8(s)) != 0) stbi__skip(s, len); break; } case 0x3B: // gif stream termination code return (stbi_uc *) s; // using '1' causes warning on some compilers default: return stbi__errpuc("unknown code", "Corrupt GIF"); } } STBI_NOTUSED(req_comp); } static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { stbi_uc *u = 0; stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); memset(g, 0, sizeof(*g)); STBI_NOTUSED(ri); u = stbi__gif_load_next(s, g, comp, req_comp); if (u == (stbi_uc *) s) u = 0; // end of animated gif marker if (u) { *x = g->w; *y = g->h; if (req_comp && req_comp != 4) u = stbi__convert_format(u, 4, req_comp, g->w, g->h); } else if (g->out) STBI_FREE(g->out); STBI_FREE(g); return u; } static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) { return stbi__gif_info_raw(s,x,y,comp); } #endif // ************************************************************************************************* // Radiance RGBE HDR loader // originally by Nicolas Schulz #ifndef STBI_NO_HDR static int stbi__hdr_test_core(stbi__context *s, const char *signature) { int i; for (i=0; signature[i]; ++i) if (stbi__get8(s) != signature[i]) return 0; stbi__rewind(s); return 1; } static int stbi__hdr_test(stbi__context* s) { int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); stbi__rewind(s); if(!r) { r = stbi__hdr_test_core(s, "#?RGBE\n"); stbi__rewind(s); } return r; } #define STBI__HDR_BUFLEN 1024 static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) { int len=0; char c = '\0'; c = (char) stbi__get8(z); while (!stbi__at_eof(z) && c != '\n') { buffer[len++] = c; if (len == STBI__HDR_BUFLEN-1) { // flush to end of line while (!stbi__at_eof(z) && stbi__get8(z) != '\n') ; break; } c = (char) stbi__get8(z); } buffer[len] = 0; return buffer; } static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) { if ( input[3] != 0 ) { float f1; // Exponent f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); if (req_comp <= 2) output[0] = (input[0] + input[1] + input[2]) * f1 / 3; else { output[0] = input[0] * f1; output[1] = input[1] * f1; output[2] = input[2] * f1; } if (req_comp == 2) output[1] = 1; if (req_comp == 4) output[3] = 1; } else { switch (req_comp) { case 4: output[3] = 1; /* fallthrough */ case 3: output[0] = output[1] = output[2] = 0; break; case 2: output[1] = 1; /* fallthrough */ case 1: output[0] = 0; break; } } } static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { char buffer[STBI__HDR_BUFLEN]; char *token; int valid = 0; int width, height; stbi_uc *scanline; float *hdr_data; int len; unsigned char count, value; int i, j, k, c1,c2, z; const char *headerToken; STBI_NOTUSED(ri); // Check identifier headerToken = stbi__hdr_gettoken(s,buffer); if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) return stbi__errpf("not HDR", "Corrupt HDR image"); // Parse header for(;;) { token = stbi__hdr_gettoken(s,buffer); if (token[0] == 0) break; if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; } if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); // Parse width and height // can't use sscanf() if we're not using stdio! token = stbi__hdr_gettoken(s,buffer); if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); token += 3; height = (int) strtol(token, &token, 10); while (*token == ' ') ++token; if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); token += 3; width = (int) strtol(token, NULL, 10); *x = width; *y = height; if (comp) *comp = 3; if (req_comp == 0) req_comp = 3; if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) return stbi__errpf("too large", "HDR image is too large"); // Read data hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); if (!hdr_data) return stbi__errpf("outofmem", "Out of memory"); // Load image data // image data is stored as some number of sca if ( width < 8 || width >= 32768) { // Read flat data for (j=0; j < height; ++j) { for (i=0; i < width; ++i) { stbi_uc rgbe[4]; main_decode_loop: stbi__getn(s, rgbe, 4); stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); } } } else { // Read RLE-encoded data scanline = NULL; for (j = 0; j < height; ++j) { c1 = stbi__get8(s); c2 = stbi__get8(s); len = stbi__get8(s); if (c1 != 2 || c2 != 2 || (len & 0x80)) { // not run-length encoded, so we have to actually use THIS data as a decoded // pixel (note this can't be a valid pixel--one of RGB must be >= 128) stbi_uc rgbe[4]; rgbe[0] = (stbi_uc) c1; rgbe[1] = (stbi_uc) c2; rgbe[2] = (stbi_uc) len; rgbe[3] = (stbi_uc) stbi__get8(s); stbi__hdr_convert(hdr_data, rgbe, req_comp); i = 1; j = 0; STBI_FREE(scanline); goto main_decode_loop; // yes, this makes no sense } len <<= 8; len |= stbi__get8(s); if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } if (scanline == NULL) { scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); if (!scanline) { STBI_FREE(hdr_data); return stbi__errpf("outofmem", "Out of memory"); } } for (k = 0; k < 4; ++k) { int nleft; i = 0; while ((nleft = width - i) > 0) { count = stbi__get8(s); if (count > 128) { // Run value = stbi__get8(s); count -= 128; if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = value; } else { // Dump if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = stbi__get8(s); } } } for (i=0; i < width; ++i) stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); } if (scanline) STBI_FREE(scanline); } return hdr_data; } static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) { char buffer[STBI__HDR_BUFLEN]; char *token; int valid = 0; int dummy; if (!x) x = &dummy; if (!y) y = &dummy; if (!comp) comp = &dummy; if (stbi__hdr_test(s) == 0) { stbi__rewind( s ); return 0; } for(;;) { token = stbi__hdr_gettoken(s,buffer); if (token[0] == 0) break; if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; } if (!valid) { stbi__rewind( s ); return 0; } token = stbi__hdr_gettoken(s,buffer); if (strncmp(token, "-Y ", 3)) { stbi__rewind( s ); return 0; } token += 3; *y = (int) strtol(token, &token, 10); while (*token == ' ') ++token; if (strncmp(token, "+X ", 3)) { stbi__rewind( s ); return 0; } token += 3; *x = (int) strtol(token, NULL, 10); *comp = 3; return 1; } #endif // STBI_NO_HDR #ifndef STBI_NO_BMP static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) { void *p; stbi__bmp_data info; info.all_a = 255; p = stbi__bmp_parse_header(s, &info); stbi__rewind( s ); if (p == NULL) return 0; if (x) *x = s->img_x; if (y) *y = s->img_y; if (comp) *comp = info.ma ? 4 : 3; return 1; } #endif #ifndef STBI_NO_PSD static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) { int channelCount, dummy; if (!x) x = &dummy; if (!y) y = &dummy; if (!comp) comp = &dummy; if (stbi__get32be(s) != 0x38425053) { stbi__rewind( s ); return 0; } if (stbi__get16be(s) != 1) { stbi__rewind( s ); return 0; } stbi__skip(s, 6); channelCount = stbi__get16be(s); if (channelCount < 0 || channelCount > 16) { stbi__rewind( s ); return 0; } *y = stbi__get32be(s); *x = stbi__get32be(s); if (stbi__get16be(s) != 8) { stbi__rewind( s ); return 0; } if (stbi__get16be(s) != 3) { stbi__rewind( s ); return 0; } *comp = 4; return 1; } #endif #ifndef STBI_NO_PIC static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) { int act_comp=0,num_packets=0,chained,dummy; stbi__pic_packet packets[10]; if (!x) x = &dummy; if (!y) y = &dummy; if (!comp) comp = &dummy; if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { stbi__rewind(s); return 0; } stbi__skip(s, 88); *x = stbi__get16be(s); *y = stbi__get16be(s); if (stbi__at_eof(s)) { stbi__rewind( s); return 0; } if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { stbi__rewind( s ); return 0; } stbi__skip(s, 8); do { stbi__pic_packet *packet; if (num_packets==sizeof(packets)/sizeof(packets[0])) return 0; packet = &packets[num_packets++]; chained = stbi__get8(s); packet->size = stbi__get8(s); packet->type = stbi__get8(s); packet->channel = stbi__get8(s); act_comp |= packet->channel; if (stbi__at_eof(s)) { stbi__rewind( s ); return 0; } if (packet->size != 8) { stbi__rewind( s ); return 0; } } while (chained); *comp = (act_comp & 0x10 ? 4 : 3); return 1; } #endif // ************************************************************************************************* // Portable Gray Map and Portable Pixel Map loader // by Ken Miller // // PGM: http://netpbm.sourceforge.net/doc/pgm.html // PPM: http://netpbm.sourceforge.net/doc/ppm.html // // Known limitations: // Does not support comments in the header section // Does not support ASCII image data (formats P2 and P3) // Does not support 16-bit-per-channel #ifndef STBI_NO_PNM static int stbi__pnm_test(stbi__context *s) { char p, t; p = (char) stbi__get8(s); t = (char) stbi__get8(s); if (p != 'P' || (t != '5' && t != '6')) { stbi__rewind( s ); return 0; } return 1; } static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { stbi_uc *out; STBI_NOTUSED(ri); if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) return 0; *x = s->img_x; *y = s->img_y; if (comp) *comp = s->img_n; if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) return stbi__errpuc("too large", "PNM too large"); out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); if (!out) return stbi__errpuc("outofmem", "Out of memory"); stbi__getn(s, out, s->img_n * s->img_x * s->img_y); if (req_comp && req_comp != s->img_n) { out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); if (out == NULL) return out; // stbi__convert_format frees input on failure } return out; } static int stbi__pnm_isspace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; } static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) { for (;;) { while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) *c = (char) stbi__get8(s); if (stbi__at_eof(s) || *c != '#') break; while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) *c = (char) stbi__get8(s); } } static int stbi__pnm_isdigit(char c) { return c >= '0' && c <= '9'; } static int stbi__pnm_getinteger(stbi__context *s, char *c) { int value = 0; while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { value = value*10 + (*c - '0'); *c = (char) stbi__get8(s); } return value; } static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) { int maxv, dummy; char c, p, t; if (!x) x = &dummy; if (!y) y = &dummy; if (!comp) comp = &dummy; stbi__rewind(s); // Get identifier p = (char) stbi__get8(s); t = (char) stbi__get8(s); if (p != 'P' || (t != '5' && t != '6')) { stbi__rewind(s); return 0; } *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm c = (char) stbi__get8(s); stbi__pnm_skip_whitespace(s, &c); *x = stbi__pnm_getinteger(s, &c); // read width stbi__pnm_skip_whitespace(s, &c); *y = stbi__pnm_getinteger(s, &c); // read height stbi__pnm_skip_whitespace(s, &c); maxv = stbi__pnm_getinteger(s, &c); // read max value if (maxv > 255) return stbi__err("max value > 255", "PPM image not 8-bit"); else return 1; } #endif static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) { #ifndef STBI_NO_JPEG if (stbi__jpeg_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_PNG if (stbi__png_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_GIF if (stbi__gif_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_BMP if (stbi__bmp_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_PSD if (stbi__psd_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_PIC if (stbi__pic_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_PNM if (stbi__pnm_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_HDR if (stbi__hdr_info(s, x, y, comp)) return 1; #endif // test tga last because it's a crappy test! #ifndef STBI_NO_TGA if (stbi__tga_info(s, x, y, comp)) return 1; #endif return stbi__err("unknown image type", "Image not of any known type, or corrupt"); } #ifndef STBI_NO_STDIO STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) { FILE *f = stbi__fopen(filename, "rb"); int result; if (!f) return stbi__err("can't fopen", "Unable to open file"); result = stbi_info_from_file(f, x, y, comp); fclose(f); return result; } STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) { int r; stbi__context s; long pos = ftell(f); stbi__start_file(&s, f); r = stbi__info_main(&s,x,y,comp); fseek(f,pos,SEEK_SET); return r; } #endif // !STBI_NO_STDIO STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) { stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__info_main(&s,x,y,comp); } STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); return stbi__info_main(&s,x,y,comp); } #endif // STB_IMAGE_IMPLEMENTATION /* revision history: 2.16 (2017-07-23) all functions have 16-bit variants; STBI_NO_STDIO works again; compilation fixes; fix rounding in unpremultiply; optimize vertical flip; disable raw_len validation; documentation fixes 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; warning fixes; disable run-time SSE detection on gcc; uniform handling of optional "return" values; thread-safe initialization of zlib tables 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes 2.11 (2016-04-02) allocate large structures on the stack remove white matting for transparent PSD fix reported channel count for PNG & BMP re-enable SSE2 in non-gcc 64-bit support RGB-formatted JPEG read 16-bit PNGs (only as 8-bit) 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED 2.09 (2016-01-16) allow comments in PNM files 16-bit-per-pixel TGA (not bit-per-component) info() for TGA could break due to .hdr handling info() for BMP to shares code instead of sloppy parse can use STBI_REALLOC_SIZED if allocator doesn't support realloc code cleanup 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA 2.07 (2015-09-13) fix compiler warnings partial animated GIF support limited 16-bpc PSD support #ifdef unused functions bug with < 92 byte PIC,PNM,HDR,TGA 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit 2.03 (2015-04-12) extra corruption checking (mmozeiko) stbi_set_flip_vertically_on_load (nguillemot) fix NEON support; fix mingw support 2.02 (2015-01-19) fix incorrect assert, fix warning 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) progressive JPEG (stb) PGM/PPM support (Ken Miller) STBI_MALLOC,STBI_REALLOC,STBI_FREE GIF bugfix -- seemingly never worked STBI_NO_*, STBI_ONLY_* 1.48 (2014-12-14) fix incorrectly-named assert() 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) optimize PNG (ryg) fix bug in interlaced PNG with user-specified channel count (stb) 1.46 (2014-08-26) fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG 1.45 (2014-08-16) fix MSVC-ARM internal compiler error by wrapping malloc 1.44 (2014-08-07) various warning fixes from Ronny Chevalier 1.43 (2014-07-15) fix MSVC-only compiler problem in code changed in 1.42 1.42 (2014-07-09) don't define _CRT_SECURE_NO_WARNINGS (affects user code) fixes to stbi__cleanup_jpeg path added STBI_ASSERT to avoid requiring assert.h 1.41 (2014-06-25) fix search&replace from 1.36 that messed up comments/error messages 1.40 (2014-06-22) fix gcc struct-initialization warning 1.39 (2014-06-15) fix to TGA optimization when req_comp != number of components in TGA; fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) add support for BMP version 5 (more ignored fields) 1.38 (2014-06-06) suppress MSVC warnings on integer casts truncating values fix accidental rename of 'skip' field of I/O 1.37 (2014-06-04) remove duplicate typedef 1.36 (2014-06-03) convert to header file single-file library if de-iphone isn't set, load iphone images color-swapped instead of returning NULL 1.35 (2014-05-27) various warnings fix broken STBI_SIMD path fix bug where stbi_load_from_file no longer left file pointer in correct place fix broken non-easy path for 32-bit BMP (possibly never used) TGA optimization by Arseny Kapoulkine 1.34 (unknown) use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case 1.33 (2011-07-14) make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements 1.32 (2011-07-13) support for "info" function for all supported filetypes (SpartanJ) 1.31 (2011-06-20) a few more leak fixes, bug in PNG handling (SpartanJ) 1.30 (2011-06-11) added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) removed deprecated format-specific test/load functions removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) fix inefficiency in decoding 32-bit BMP (David Woo) 1.29 (2010-08-16) various warning fixes from Aurelien Pocheville 1.28 (2010-08-01) fix bug in GIF palette transparency (SpartanJ) 1.27 (2010-08-01) cast-to-stbi_uc to fix warnings 1.26 (2010-07-24) fix bug in file buffering for PNG reported by SpartanJ 1.25 (2010-07-17) refix trans_data warning (Won Chun) 1.24 (2010-07-12) perf improvements reading from files on platforms with lock-heavy fgetc() minor perf improvements for jpeg deprecated type-specific functions so we'll get feedback if they're needed attempt to fix trans_data warning (Won Chun) 1.23 fixed bug in iPhone support 1.22 (2010-07-10) removed image *writing* support stbi_info support from Jetro Lauha GIF support from Jean-Marc Lienher iPhone PNG-extensions from James Brown warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) 1.21 fix use of 'stbi_uc' in header (reported by jon blow) 1.20 added support for Softimage PIC, by Tom Seddon 1.19 bug in interlaced PNG corruption check (found by ryg) 1.18 (2008-08-02) fix a threading bug (local mutable static) 1.17 support interlaced PNG 1.16 major bugfix - stbi__convert_format converted one too many pixels 1.15 initialize some fields for thread safety 1.14 fix threadsafe conversion bug header-file-only version (#define STBI_HEADER_FILE_ONLY before including) 1.13 threadsafe 1.12 const qualifiers in the API 1.11 Support installable IDCT, colorspace conversion routines 1.10 Fixes for 64-bit (don't use "unsigned long") optimized upsampling by Fabian "ryg" Giesen 1.09 Fix format-conversion for PSD code (bad global variables!) 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz 1.07 attempt to fix C++ warning/errors again 1.06 attempt to fix C++ warning/errors again 1.05 fix TGA loading to return correct *comp and use good luminance calc 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR 1.02 support for (subset of) HDR files, float interface for preferred access to them 1.01 fix bug: possible bug in handling right-side up bmps... not sure fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all 1.00 interface to zlib that skips zlib header 0.99 correct handling of alpha in palette 0.98 TGA loader by lonesock; dynamically add loaders (untested) 0.97 jpeg errors on too large a file; also catch another malloc failure 0.96 fix detection of invalid v value - particleman@mollyrocket forum 0.95 during header scan, seek to markers in case of padding 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same 0.93 handle jpegtran output; verbose errors 0.92 read 4,8,16,24,32-bit BMP files of several formats 0.91 output 24-bit Windows 3.0 BMP files 0.90 fix a few more warnings; bump version number to approach 1.0 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd 0.60 fix compiling as c++ 0.59 fix warnings: merge Dave Moore's -Wall fixes 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available 0.56 fix bug: zlib uncompressed mode len vs. nlen 0.55 fix bug: restart_interval not initialized to 0 0.54 allow NULL for 'int *comp' 0.53 fix bug in png 3->4; speedup png decoding 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments 0.51 obey req_comp requests, 1-component jpegs return as 1-component, on 'test' only check type, not whether we support this variant 0.50 (2006-11-19) first released version */ /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ vkmark-2017.08+git20220909/src/util.cpp000066400000000000000000000071671441550741700170700ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include #include #include #include "util.h" #include #define STB_IMAGE_IMPLEMENTATION #define STBI_ONLY_PNG #define STBI_ONLY_JPEG #include "stb_image.h" namespace { std::string data_dir; } std::vector Util::split(std::string const& src, char delim) { std::vector elements; std::stringstream ss{src, std::ios_base::ate | std::ios_base::in | std::ios_base::out}; // std::getline() doesn't deal with trailing delimiters in the // way we want it to (i.e. "a:b:" => {"a","b",""}), so fix this // by appending an extra delimiter in such cases. if (!src.empty() && src.back() == delim) ss.put(delim); std::string item; while(std::getline(ss, item, delim)) elements.push_back(item); return elements; } uint64_t Util::get_timestamp_us() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t const now = static_cast(ts.tv_sec) * 1000000 + static_cast(ts.tv_nsec) / 1000; return now; } void Util::set_data_dir(std::string const& dir) { data_dir = dir; } std::string Util::get_data_file_path(std::string const& rel_path) { if (data_dir.empty()) throw std::logic_error("Data directory not set!"); return data_dir + "/" + rel_path; } std::vector Util::read_data_file(std::string const& rel_path) { auto const path = get_data_file_path(rel_path); std::ifstream ifs{path, std::ios::ate | std::ios::binary}; if (!ifs) throw std::runtime_error{"Failed to open file " + path}; auto const file_size = ifs.tellg(); std::vector buffer(file_size); ifs.seekg(0); ifs.read(buffer.data(), file_size); return buffer; } Util::Image::Image() : data{nullptr}, size{0}, width{0}, height{0} { } Util::Image::~Image() { if (data) stbi_image_free(data); } Util::Image::Image(Image&& other) : data{other.data}, size{other.size}, width{other.width}, height{other.height} { other.data = nullptr; } Util::Image& Util::Image::operator=(Image&& other) { if (data) stbi_image_free(data); data = other.data; size = other.size; width = other.width; height = other.height; other.data = nullptr; return *this; } Util::Image Util::read_image_file(std::string const& rel_path) { auto const path = get_data_file_path(rel_path); int w = 0; int h = 0; int c = 0; Image image; image.data = stbi_load(path.c_str(), &w, &h, &c, STBI_rgb_alpha); if (!image.data) { throw std::runtime_error{ "Failed to read image file " + path + ": " + stbi_failure_reason()}; } image.width = static_cast(w); image.height = static_cast(h); image.size = image.width * image.height * 4; return image; } vkmark-2017.08+git20220909/src/util.h000066400000000000000000000040051441550741700165210ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include #include #include namespace Util { std::vector split(std::string const& src, char delim); uint64_t get_timestamp_us(); void set_data_dir(std::string const& path); std::string get_data_file_path(std::string const& rel_path); std::vector read_data_file(std::string const& rel_path); struct Image { Image(); ~Image(); Image(Image&& image); Image& operator=(Image&& image); unsigned char* data; size_t size; size_t width; size_t height; }; Image read_image_file(std::string const& rel_path); template T from_string(std::string const& str) { std::stringstream ss{str}; T ret{}; ss >> ret; return ret; } template struct RAIIHelper { RAIIHelper(Init&& init, Deinit&& deinit) : deinit{deinit} { init(); } ~RAIIHelper() { deinit(); } Deinit deinit; }; template RAIIHelper make_raii(Init&& init, Deinit&& deinit) { return {std::move(init), std::move(deinit)}; } template RAIIHelper on_scope_exit(Deinit&& deinit) { return {[]{}, std::move(deinit)}; } } vkmark-2017.08+git20220909/src/vkutil/000077500000000000000000000000001441550741700167125ustar00rootroot00000000000000vkmark-2017.08+git20220909/src/vkutil/buffer_builder.cpp000066400000000000000000000056101441550741700223770ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "buffer_builder.h" #include "find_matching_memory_type.h" #include "vulkan_state.h" vkutil::BufferBuilder::BufferBuilder(VulkanState& vulkan) : vulkan{vulkan}, size{0}, memory_out_ptr{nullptr} { } vkutil::BufferBuilder& vkutil::BufferBuilder::set_size(size_t size_) { size = size_; return *this; } vkutil::BufferBuilder& vkutil::BufferBuilder::set_usage(vk::BufferUsageFlags usage_) { usage = usage_; return *this; } vkutil::BufferBuilder& vkutil::BufferBuilder::set_memory_properties( vk::MemoryPropertyFlags memory_properties_) { memory_properties = memory_properties_; return *this; } vkutil::BufferBuilder& vkutil::BufferBuilder::set_memory_out( vk::DeviceMemory& memory_out) { memory_out_ptr = &memory_out; return *this; } ManagedResource vkutil::BufferBuilder::build() { auto const vertex_buffer_create_info = vk::BufferCreateInfo{} .setSize(size) .setUsage(usage) .setSharingMode(vk::SharingMode::eExclusive); auto vk_buffer = ManagedResource{ vulkan.device().createBuffer(vertex_buffer_create_info), [vptr=&vulkan] (auto const& b) { vptr->device().destroyBuffer(b); }}; auto const mem_requirements = vulkan.device().getBufferMemoryRequirements(vk_buffer); auto const mem_type = vkutil::find_matching_memory_type( vulkan, mem_requirements, memory_properties); auto const memory_allocate_info = vk::MemoryAllocateInfo{} .setAllocationSize(mem_requirements.size) .setMemoryTypeIndex(mem_type); auto vk_mem = ManagedResource{ vulkan.device().allocateMemory(memory_allocate_info), [vptr=&vulkan] (auto const& m) { vptr->device().freeMemory(m); }}; vulkan.device().bindBufferMemory(vk_buffer, vk_mem, 0); if (memory_out_ptr) *memory_out_ptr = vk_mem.raw; return ManagedResource{ vk_buffer.steal(), [vptr=&vulkan, mem=vk_mem.steal()] (auto const& b) { vptr->device().freeMemory(mem); vptr->device().destroyBuffer(b); }}; } vkmark-2017.08+git20220909/src/vkutil/buffer_builder.h000066400000000000000000000030151441550741700220410ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" class VulkanState; namespace vkutil { class BufferBuilder { public: BufferBuilder(VulkanState& vulkan); BufferBuilder& set_size(size_t size); BufferBuilder& set_usage(vk::BufferUsageFlags usage); BufferBuilder& set_memory_properties(vk::MemoryPropertyFlags memory_properties); BufferBuilder& set_memory_out(vk::DeviceMemory& memory_out); ManagedResource build(); private: uint32_t find_matching_memory_type_for(vk::MemoryRequirements const& requirements); VulkanState& vulkan; size_t size; vk::BufferUsageFlags usage; vk::MemoryPropertyFlags memory_properties; vk::DeviceMemory* memory_out_ptr; }; } vkmark-2017.08+git20220909/src/vkutil/copy_buffer.cpp000066400000000000000000000036471441550741700217330ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "copy_buffer.h" #include "one_time_command_buffer.h" void vkutil::copy_buffer( VulkanState& vulkan, vk::Buffer src, vk::Buffer dst, vk::DeviceSize size) { OneTimeCommandBuffer otcb{vulkan}; auto const region = vk::BufferCopy{}.setSize(size); otcb.command_buffer().copyBuffer(src, dst, region); otcb.submit(); } void vkutil::copy_buffer_to_image( VulkanState& vulkan, vk::Buffer src, vk::Image dst, vk::Extent2D extent) { OneTimeCommandBuffer otcb{vulkan}; auto const image_subresource_layers = vk::ImageSubresourceLayers{} .setAspectMask(vk::ImageAspectFlagBits::eColor) .setMipLevel(0) .setBaseArrayLayer(0) .setLayerCount(1); auto const region = vk::BufferImageCopy{} .setBufferOffset(0) .setBufferRowLength(0) .setBufferImageHeight(0) .setImageSubresource(image_subresource_layers) .setImageOffset({0, 0, 0}) .setImageExtent({extent.width, extent.height, 1}); otcb.command_buffer().copyBufferToImage( src, dst, vk::ImageLayout::eTransferDstOptimal, region); otcb.submit(); } vkmark-2017.08+git20220909/src/vkutil/copy_buffer.h000066400000000000000000000021411441550741700213640ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include class VulkanState; namespace vkutil { void copy_buffer( VulkanState& vulkan, vk::Buffer src, vk::Buffer dst, vk::DeviceSize size); void copy_buffer_to_image( VulkanState& vulkan, vk::Buffer src, vk::Image dst, vk::Extent2D extent); } vkmark-2017.08+git20220909/src/vkutil/descriptor_set_builder.cpp000066400000000000000000000135451441550741700241650ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "descriptor_set_builder.h" #include "vulkan_state.h" vkutil::DescriptorSetBuilder::Info::Info() : descriptor_type{vk::DescriptorType::eSampler}, buffer{nullptr}, offset{0}, range{0}, image_view{nullptr}, sampler{nullptr} { } vkutil::DescriptorSetBuilder::DescriptorSetBuilder(VulkanState& vulkan) : vulkan{vulkan}, info{1}, layout_out_ptr{nullptr} { } vkutil::DescriptorSetBuilder& vkutil::DescriptorSetBuilder::set_type( vk::DescriptorType type) { info.back().descriptor_type = type; return *this; } vkutil::DescriptorSetBuilder& vkutil::DescriptorSetBuilder::set_stage_flags( vk::ShaderStageFlags stage_flags_) { info.back().stage_flags = stage_flags_; return *this; } vkutil::DescriptorSetBuilder& vkutil::DescriptorSetBuilder::set_buffer( vk::Buffer& buffer_, size_t offset_, size_t range_) { info.back().buffer = &buffer_; info.back().offset = offset_; info.back().range = range_; return *this; } vkutil::DescriptorSetBuilder& vkutil::DescriptorSetBuilder::set_image_view( vk::ImageView& image_view, vk::Sampler& sampler) { info.back().image_view = &image_view; info.back().sampler = &sampler; return *this; } vkutil::DescriptorSetBuilder& vkutil::DescriptorSetBuilder::set_layout_out( vk::DescriptorSetLayout& layout_out) { layout_out_ptr = &layout_out; return *this; } vkutil::DescriptorSetBuilder& vkutil::DescriptorSetBuilder::next_binding() { info.push_back({}); return *this; } ManagedResource vkutil::DescriptorSetBuilder::build() { // Layout std::vector bindings; for (auto i = 0u; i < info.size(); ++i) { bindings.push_back( vk::DescriptorSetLayoutBinding{} .setBinding(i) .setDescriptorType(info[i].descriptor_type) .setDescriptorCount(1) .setStageFlags(info[i].stage_flags)); } auto const descriptor_set_layout_create_info = vk::DescriptorSetLayoutCreateInfo{} .setBindingCount(bindings.size()) .setPBindings(bindings.data()); auto descriptor_set_layout = ManagedResource{ vulkan.device().createDescriptorSetLayout(descriptor_set_layout_create_info), [vptr=&vulkan] (auto const& dsl) { vptr->device().destroyDescriptorSetLayout(dsl); }}; // Descriptor pool and sets std::vector pool_sizes; for (auto i = 0u; i < info.size(); ++i) { pool_sizes.push_back(vk::DescriptorPoolSize{} .setDescriptorCount(1) .setType(info[i].descriptor_type)); } auto const descriptor_pool_create_info = vk::DescriptorPoolCreateInfo{} .setPoolSizeCount(pool_sizes.size()) .setPPoolSizes(pool_sizes.data()) .setMaxSets(1); auto descriptor_pool = ManagedResource{ vulkan.device().createDescriptorPool(descriptor_pool_create_info), [vptr=&vulkan] (auto const& p) { vptr->device().destroyDescriptorPool(p); }}; auto const descriptor_set_allocate_info = vk::DescriptorSetAllocateInfo{} .setDescriptorPool(descriptor_pool) .setDescriptorSetCount(1) .setPSetLayouts(&descriptor_set_layout.raw); auto descriptor_set = vulkan.device().allocateDescriptorSets(descriptor_set_allocate_info); // Update descriptor set std::vector write_descriptor_sets(info.size()); // Not all info slots are used, depending on descriptor types std::vector descriptor_buffer_infos(info.size()); std::vector descriptor_image_infos(info.size()); for (auto i = 0u; i < info.size(); ++i) { write_descriptor_sets[i] .setDstSet(descriptor_set[0]) .setDstBinding(i) .setDstArrayElement(0) .setDescriptorType(info[i].descriptor_type) .setDescriptorCount(1); if (info[i].buffer) { descriptor_buffer_infos[i] .setBuffer(*info[i].buffer) .setOffset(info[i].offset) .setRange(info[i].range); write_descriptor_sets[i].setPBufferInfo(&descriptor_buffer_infos[i]); } else if (info[i].image_view) { descriptor_image_infos[i] .setImageLayout(vk::ImageLayout::eShaderReadOnlyOptimal) .setImageView(*info[i].image_view) .setSampler(*info[i].sampler); write_descriptor_sets[i].setPImageInfo(&descriptor_image_infos[i]); } } vulkan.device().updateDescriptorSets(write_descriptor_sets, {}); if (layout_out_ptr) *layout_out_ptr = descriptor_set_layout.raw; return ManagedResource{ std::move(descriptor_set[0]), [vptr=&vulkan, layout=descriptor_set_layout.steal(), pool=descriptor_pool.steal()] (auto const&) { vptr->device().destroyDescriptorPool(pool); vptr->device().destroyDescriptorSetLayout(layout); }}; } vkmark-2017.08+git20220909/src/vkutil/descriptor_set_builder.h000066400000000000000000000035321441550741700236250ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" class VulkanState; namespace vkutil { class DescriptorSetBuilder { public: DescriptorSetBuilder(VulkanState& vulkan); DescriptorSetBuilder& set_type(vk::DescriptorType type); DescriptorSetBuilder& set_stage_flags(vk::ShaderStageFlags stage_flags); DescriptorSetBuilder& set_buffer(vk::Buffer& buffer_, size_t offset_, size_t range_); DescriptorSetBuilder& set_image_view(vk::ImageView& image_view, vk::Sampler& sampler); DescriptorSetBuilder& set_layout_out(vk::DescriptorSetLayout& layout_out); DescriptorSetBuilder& next_binding(); ManagedResource build(); private: VulkanState& vulkan; struct Info { Info(); vk::DescriptorType descriptor_type; vk::ShaderStageFlags stage_flags; vk::Buffer* buffer; size_t offset; size_t range; vk::ImageView* image_view; vk::Sampler* sampler; }; std::vector info; vk::DescriptorSetLayout* layout_out_ptr; }; } vkmark-2017.08+git20220909/src/vkutil/find_matching_memory_type.cpp000066400000000000000000000026671441550741700246540ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "find_matching_memory_type.h" #include "vulkan_state.h" uint32_t vkutil::find_matching_memory_type( VulkanState& vulkan, vk::MemoryRequirements const& requirements, vk::MemoryPropertyFlags const& memory_properties) { auto const properties = vulkan.physical_device().getMemoryProperties(); for (uint32_t i = 0; i < properties.memoryTypeCount; i++) { if ((requirements.memoryTypeBits & (1 << i)) && (properties.memoryTypes[i].propertyFlags & memory_properties) == memory_properties) { return i; } } throw std::runtime_error("Couldn't find matching memory type"); } vkmark-2017.08+git20220909/src/vkutil/find_matching_memory_type.h000066400000000000000000000020421441550741700243040ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include class VulkanState; namespace vkutil { uint32_t find_matching_memory_type( VulkanState& vulkan, vk::MemoryRequirements const& requirements, vk::MemoryPropertyFlags const& memory_properties); } vkmark-2017.08+git20220909/src/vkutil/framebuffer_builder.cpp000066400000000000000000000037211441550741700234130ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "framebuffer_builder.h" #include "vulkan_state.h" vkutil::FramebufferBuilder::FramebufferBuilder(VulkanState& vulkan) : vulkan{vulkan} { } vkutil::FramebufferBuilder& vkutil::FramebufferBuilder::set_render_pass( vk::RenderPass render_pass_) { render_pass = render_pass_; return *this; } vkutil::FramebufferBuilder& vkutil::FramebufferBuilder::set_image_views( std::vector const& image_views_) { image_views = image_views_; return *this; } vkutil::FramebufferBuilder& vkutil::FramebufferBuilder::set_extent( vk::Extent2D extent_) { extent = extent_; return *this; } ManagedResource vkutil::FramebufferBuilder::build() { auto const framebuffer_create_info = vk::FramebufferCreateInfo{} .setRenderPass(render_pass) .setAttachmentCount(image_views.size()) .setPAttachments(image_views.data()) .setWidth(extent.width) .setHeight(extent.height) .setLayers(1); return ManagedResource{ vulkan.device().createFramebuffer(framebuffer_create_info), [vptr=&vulkan] (auto const& fb) { vptr->device().destroyFramebuffer(fb); }}; } vkmark-2017.08+git20220909/src/vkutil/framebuffer_builder.h000066400000000000000000000026201441550741700230550ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" #include class VulkanState; namespace vkutil { class FramebufferBuilder { public: FramebufferBuilder(VulkanState& vulkan); FramebufferBuilder& set_render_pass(vk::RenderPass render_pass); FramebufferBuilder& set_image_views(std::vector const& image_view); FramebufferBuilder& set_extent(vk::Extent2D extent); ManagedResource build(); private: VulkanState& vulkan; vk::RenderPass render_pass; std::vector image_views; vk::Extent2D extent; }; } vkmark-2017.08+git20220909/src/vkutil/image_builder.cpp000066400000000000000000000065651441550741700222220ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "image_builder.h" #include "find_matching_memory_type.h" #include "vulkan_state.h" vkutil::ImageBuilder::ImageBuilder(VulkanState& vulkan) : vulkan{vulkan}, format{vk::Format::eUndefined}, tiling{vk::ImageTiling::eOptimal}, initial_layout{vk::ImageLayout::eUndefined} { } vkutil::ImageBuilder& vkutil::ImageBuilder::set_extent(vk::Extent2D extent_) { extent = extent_; return *this; } vkutil::ImageBuilder& vkutil::ImageBuilder::set_format(vk::Format format_) { format = format_; return *this; } vkutil::ImageBuilder& vkutil::ImageBuilder::set_tiling(vk::ImageTiling tiling_) { tiling = tiling_; return *this; } vkutil::ImageBuilder& vkutil::ImageBuilder::set_usage(vk::ImageUsageFlags usage_) { usage = usage_; return *this; } vkutil::ImageBuilder& vkutil::ImageBuilder::set_memory_properties( vk::MemoryPropertyFlags memory_properties_) { memory_properties = memory_properties_; return *this; } vkutil::ImageBuilder& vkutil::ImageBuilder::set_initial_layout(vk::ImageLayout initial_layout_) { initial_layout = initial_layout_; return *this; } ManagedResource vkutil::ImageBuilder::build() { auto const image_create_info = vk::ImageCreateInfo{} .setImageType(vk::ImageType::e2D) .setExtent({extent.width, extent.height, 1}) .setMipLevels(1) .setArrayLayers(1) .setFormat(format) .setTiling(tiling) .setInitialLayout(initial_layout) .setUsage(usage) .setSamples(vk::SampleCountFlagBits::e1) .setSharingMode(vk::SharingMode::eExclusive); auto vk_image = ManagedResource{ vulkan.device().createImage(image_create_info), [vptr=&vulkan] (auto const& i) { vptr->device().destroyImage(i); }}; auto const req = vulkan.device().getImageMemoryRequirements(vk_image); auto const memory_type_index = vkutil::find_matching_memory_type( vulkan, req, memory_properties); auto const memory_allocate_info = vk::MemoryAllocateInfo{} .setAllocationSize(req.size) .setMemoryTypeIndex(memory_type_index); auto vk_mem = ManagedResource{ vulkan.device().allocateMemory(memory_allocate_info), [vptr=&vulkan] (auto const& m) { vptr->device().freeMemory(m); }}; vulkan.device().bindImageMemory(vk_image, vk_mem, 0); return ManagedResource{ vk_image.steal(), [vptr=&vulkan, mem=vk_mem.steal()] (auto const& i) { vptr->device().freeMemory(mem); vptr->device().destroyImage(i); }}; } vkmark-2017.08+git20220909/src/vkutil/image_builder.h000066400000000000000000000031341441550741700216540ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" class VulkanState; namespace vkutil { class ImageBuilder { public: ImageBuilder(VulkanState& vulkan); ImageBuilder& set_extent(vk::Extent2D extent); ImageBuilder& set_format(vk::Format format); ImageBuilder& set_tiling(vk::ImageTiling tiling); ImageBuilder& set_usage(vk::ImageUsageFlags usage); ImageBuilder& set_memory_properties(vk::MemoryPropertyFlags memory_properties); ImageBuilder& set_initial_layout(vk::ImageLayout initial_layout); ManagedResource build(); private: VulkanState& vulkan; vk::Extent2D extent; vk::Format format; vk::ImageTiling tiling; vk::ImageUsageFlags usage; vk::MemoryPropertyFlags memory_properties; vk::ImageLayout initial_layout; }; } vkmark-2017.08+git20220909/src/vkutil/image_view_builder.cpp000066400000000000000000000040511441550741700232400ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "image_view_builder.h" #include "vulkan_state.h" vkutil::ImageViewBuilder::ImageViewBuilder(VulkanState& vulkan) : vulkan{vulkan}, format{vk::Format::eUndefined} { } vkutil::ImageViewBuilder& vkutil::ImageViewBuilder::set_image(vk::Image image_) { image = image_; return *this; } vkutil::ImageViewBuilder& vkutil::ImageViewBuilder::set_format(vk::Format format_) { format = format_; return *this; } vkutil::ImageViewBuilder& vkutil::ImageViewBuilder::set_aspect_mask( vk::ImageAspectFlags mask) { aspect_mask = mask; return *this; } ManagedResource vkutil::ImageViewBuilder::build() { auto const image_subresource_range = vk::ImageSubresourceRange{} .setAspectMask(aspect_mask) .setBaseMipLevel(0) .setLevelCount(1) .setBaseArrayLayer(0) .setLayerCount(1); auto const image_view_create_info = vk::ImageViewCreateInfo{} .setImage(image) .setViewType(vk::ImageViewType::e2D) .setFormat(format) .setSubresourceRange(image_subresource_range); return ManagedResource{ vulkan.device().createImageView(image_view_create_info), [vptr=&vulkan] (auto const& iv) { vptr->device().destroyImageView(iv); }}; } vkmark-2017.08+git20220909/src/vkutil/image_view_builder.h000066400000000000000000000024701441550741700227100ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" class VulkanState; namespace vkutil { class ImageViewBuilder { public: ImageViewBuilder(VulkanState& vulkan); ImageViewBuilder& set_image(vk::Image image); ImageViewBuilder& set_format(vk::Format format); ImageViewBuilder& set_aspect_mask(vk::ImageAspectFlags mask); ManagedResource build(); private: VulkanState& vulkan; vk::Image image; vk::Format format; vk::ImageAspectFlags aspect_mask; }; } vkmark-2017.08+git20220909/src/vkutil/map_memory.cpp000066400000000000000000000023141441550741700215630ustar00rootroot00000000000000/* * Copyright © 2018 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "map_memory.h" #include "vulkan_state.h" ManagedResource vkutil::map_memory( VulkanState& vulkan, vk::DeviceMemory memory, vk::DeviceSize offset, vk::DeviceSize size, vk::MemoryMapFlags flags) { return ManagedResource{ vulkan.device().mapMemory(memory, offset, size, flags), [vptr=&vulkan, memory] (auto const&) { vptr->device().unmapMemory(memory); }}; } vkmark-2017.08+git20220909/src/vkutil/map_memory.h000066400000000000000000000021401441550741700212250ustar00rootroot00000000000000/* * Copyright © 2018 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" class VulkanState; namespace vkutil { ManagedResource map_memory( VulkanState& vulkan, vk::DeviceMemory memory, vk::DeviceSize offset, vk::DeviceSize size, vk::MemoryMapFlags flags = vk::MemoryMapFlags()); } vkmark-2017.08+git20220909/src/vkutil/one_time_command_buffer.cpp000066400000000000000000000042071441550741700242470ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "one_time_command_buffer.h" #include "vulkan_state.h" namespace { ManagedResource create_command_buffer(VulkanState& vulkan) { auto const command_buffer_allocate_info = vk::CommandBufferAllocateInfo{} .setCommandPool(vulkan.command_pool()) .setCommandBufferCount(1) .setLevel(vk::CommandBufferLevel::ePrimary); return ManagedResource{ std::move(vulkan.device().allocateCommandBuffers(command_buffer_allocate_info)[0]), [&vulkan] (auto const& cb) { vulkan.device().freeCommandBuffers(vulkan.command_pool(), cb); }}; } } vkutil::OneTimeCommandBuffer::OneTimeCommandBuffer( VulkanState& vulkan) : vulkan{vulkan}, command_buffer_{create_command_buffer(vulkan)} { auto const begin_info = vk::CommandBufferBeginInfo{} .setFlags(vk::CommandBufferUsageFlagBits::eOneTimeSubmit); command_buffer_.raw.begin(begin_info); } vk::CommandBuffer vkutil::OneTimeCommandBuffer::command_buffer() const { return command_buffer_; } void vkutil::OneTimeCommandBuffer::submit() { command_buffer_.raw.end(); auto const submit_info = vk::SubmitInfo{} .setCommandBufferCount(1) .setPCommandBuffers(&command_buffer_.raw); vulkan.graphics_queue().submit(submit_info, {}); vulkan.device().waitIdle(); } vkmark-2017.08+git20220909/src/vkutil/one_time_command_buffer.h000066400000000000000000000022211441550741700237060ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" class VulkanState; namespace vkutil { class OneTimeCommandBuffer { public: OneTimeCommandBuffer(VulkanState& vulkan); vk::CommandBuffer command_buffer() const; void submit(); private: VulkanState& vulkan; ManagedResource command_buffer_; }; } vkmark-2017.08+git20220909/src/vkutil/pipeline_builder.cpp000066400000000000000000000164451441550741700227430ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "pipeline_builder.h" #include "vulkan_state.h" namespace { ManagedResource create_shader_module( vk::Device const& device, std::vector const& code) { std::vector code_aligned(code.size() / 4 + 1); memcpy(code_aligned.data(), code.data(), code.size()); auto const shader_module_create_info = vk::ShaderModuleCreateInfo{} .setCodeSize(code.size()) .setPCode(code_aligned.data()); return ManagedResource{ device.createShaderModule(shader_module_create_info), [dptr=&device] (auto const& sm) { dptr->destroyShaderModule(sm); }}; } } vkutil::PipelineBuilder::PipelineBuilder(VulkanState& vulkan) : vulkan{vulkan}, depth_test{false}, blend{false} { } vkutil::PipelineBuilder& vkutil::PipelineBuilder::set_vertex_input( std::vector const& binding_descriptions_, std::vector const& attribute_descriptions_) { binding_descriptions = binding_descriptions_; attribute_descriptions = attribute_descriptions_; return *this; } vkutil::PipelineBuilder& vkutil::PipelineBuilder::set_vertex_shader( std::vector const& spirv) { vertex_shader_spirv = spirv; return *this; } vkutil::PipelineBuilder& vkutil::PipelineBuilder::set_fragment_shader( std::vector const& spirv) { fragment_shader_spirv = spirv; return *this; } vkutil::PipelineBuilder& vkutil::PipelineBuilder::set_depth_test(bool depth_test_) { depth_test = depth_test_; return *this; } vkutil::PipelineBuilder& vkutil::PipelineBuilder::set_extent(vk::Extent2D extent_) { extent = extent_; return *this; } vkutil::PipelineBuilder& vkutil::PipelineBuilder::set_layout(vk::PipelineLayout layout_) { layout = layout_; return *this; } vkutil::PipelineBuilder& vkutil::PipelineBuilder::set_render_pass(vk::RenderPass render_pass_) { render_pass = render_pass_; return *this; } vkutil::PipelineBuilder& vkutil::PipelineBuilder::set_blend(bool blend_) { blend = blend_; return *this; } ManagedResource vkutil::PipelineBuilder::build() { auto const vertex_shader = create_shader_module(vulkan.device(), vertex_shader_spirv); auto const fragment_shader = create_shader_module(vulkan.device(), fragment_shader_spirv); auto const vertex_shader_stage_create_info = vk::PipelineShaderStageCreateInfo{} .setStage(vk::ShaderStageFlagBits::eVertex) .setModule(vertex_shader) .setPName("main"); auto const fragment_shader_stage_create_info = vk::PipelineShaderStageCreateInfo{} .setStage(vk::ShaderStageFlagBits::eFragment) .setModule(fragment_shader) .setPName("main"); vk::PipelineShaderStageCreateInfo shader_stages[] = { vertex_shader_stage_create_info, fragment_shader_stage_create_info}; auto const vertex_input_state_create_info = vk::PipelineVertexInputStateCreateInfo{} .setVertexBindingDescriptionCount(binding_descriptions.size()) .setPVertexBindingDescriptions(binding_descriptions.data()) .setVertexAttributeDescriptionCount(attribute_descriptions.size()) .setPVertexAttributeDescriptions(attribute_descriptions.data()); auto const input_assembly_state_create_info = vk::PipelineInputAssemblyStateCreateInfo{} .setTopology(vk::PrimitiveTopology::eTriangleList) .setPrimitiveRestartEnable(false); auto const viewport = vk::Viewport{} .setWidth(extent.width) .setHeight(extent.height) .setMinDepth(0.0f) .setMaxDepth(1.0f); auto const scissor = vk::Rect2D{} .setOffset({0, 0}) .setExtent(extent); auto const viewport_state_create_info = vk::PipelineViewportStateCreateInfo{} .setViewportCount(1) .setPViewports(&viewport) .setScissorCount(1) .setPScissors(&scissor); auto const rasterization_state_create_info = vk::PipelineRasterizationStateCreateInfo{} .setDepthClampEnable(false) .setRasterizerDiscardEnable(false) .setPolygonMode(vk::PolygonMode::eFill) .setLineWidth(1.0f) .setCullMode(vk::CullModeFlagBits::eBack) .setFrontFace(vk::FrontFace::eCounterClockwise) .setDepthBiasEnable(false); auto const multisample_state_create_info = vk::PipelineMultisampleStateCreateInfo{} .setSampleShadingEnable(false) .setRasterizationSamples(vk::SampleCountFlagBits::e1); auto const blend_attach = vk::PipelineColorBlendAttachmentState{} .setColorWriteMask( vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA) .setBlendEnable(blend) .setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha) .setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha) .setColorBlendOp(vk::BlendOp::eAdd) .setSrcAlphaBlendFactor(vk::BlendFactor::eOne) .setDstAlphaBlendFactor(vk::BlendFactor::eZero) .setAlphaBlendOp(vk::BlendOp::eAdd); auto const color_blend_state_create_info = vk::PipelineColorBlendStateCreateInfo{} .setLogicOpEnable(false) .setAttachmentCount(1) .setPAttachments(&blend_attach); auto const depth_stencil_state_create_info = vk::PipelineDepthStencilStateCreateInfo{} .setDepthTestEnable(depth_test) .setDepthWriteEnable(depth_test) .setDepthCompareOp(vk::CompareOp::eLess) .setDepthBoundsTestEnable(false) .setStencilTestEnable(false); auto pipeline_create_info = vk::GraphicsPipelineCreateInfo{} .setStageCount(2) .setPStages(shader_stages) .setPVertexInputState(&vertex_input_state_create_info) .setPInputAssemblyState(&input_assembly_state_create_info) .setPViewportState(&viewport_state_create_info) .setPRasterizationState(&rasterization_state_create_info) .setPMultisampleState(&multisample_state_create_info) .setPColorBlendState(&color_blend_state_create_info) .setPDepthStencilState(&depth_stencil_state_create_info) .setLayout(layout) .setRenderPass(render_pass) .setSubpass(0); return ManagedResource{ #if VK_HEADER_VERSION > 148 vulkan.device().createGraphicsPipeline({}, pipeline_create_info).value, #else vulkan.device().createGraphicsPipeline({}, pipeline_create_info), #endif [vptr=&vulkan] (auto const& p) { vptr->device().destroyPipeline(p); }}; } vkmark-2017.08+git20220909/src/vkutil/pipeline_builder.h000066400000000000000000000040411441550741700223750ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" class VulkanState; namespace vkutil { class PipelineBuilder { public: PipelineBuilder(VulkanState& vulkan); PipelineBuilder& set_vertex_input( std::vector const& binding_descriptions, std::vector const& attribute_descriptions); PipelineBuilder& set_vertex_shader(std::vector const& spirv); PipelineBuilder& set_fragment_shader(std::vector const& spirv); PipelineBuilder& set_depth_test(bool depth_test); PipelineBuilder& set_extent(vk::Extent2D extent); PipelineBuilder& set_layout(vk::PipelineLayout layout); PipelineBuilder& set_render_pass(vk::RenderPass render_pass); PipelineBuilder& set_blend(bool blend); ManagedResource build(); private: VulkanState& vulkan; std::vector binding_descriptions; std::vector attribute_descriptions; std::vector vertex_shader_spirv; std::vector fragment_shader_spirv; bool depth_test; bool blend; vk::Extent2D extent; vk::PipelineLayout layout; vk::RenderPass render_pass; }; } vkmark-2017.08+git20220909/src/vkutil/render_pass_builder.cpp000066400000000000000000000105101441550741700234260ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "render_pass_builder.h" #include "vulkan_state.h" vkutil::RenderPassBuilder::RenderPassBuilder(VulkanState& vulkan) : vulkan{vulkan}, color_format{vk::Format::eUndefined}, depth_format{vk::Format::eUndefined}, color_load_op{vk::AttachmentLoadOp::eLoad} { } vkutil::RenderPassBuilder& vkutil::RenderPassBuilder::set_color_format(vk::Format format_) { color_format = format_; return *this; } vkutil::RenderPassBuilder& vkutil::RenderPassBuilder::set_depth_format(vk::Format format_) { depth_format = format_; return *this; } vkutil::RenderPassBuilder& vkutil::RenderPassBuilder::set_color_load_op(vk::AttachmentLoadOp load_op_) { color_load_op = load_op_; return *this; } ManagedResource vkutil::RenderPassBuilder::build() { auto const color_attachment = vk::AttachmentDescription{} .setFormat(color_format) .setSamples(vk::SampleCountFlagBits::e1) .setLoadOp(color_load_op) .setStoreOp(vk::AttachmentStoreOp::eStore) .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare) .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare) .setInitialLayout(vk::ImageLayout::eUndefined) .setFinalLayout(vk::ImageLayout::ePresentSrcKHR); auto const color_attachment_ref = vk::AttachmentReference{} .setAttachment(0) .setLayout(vk::ImageLayout::eColorAttachmentOptimal); auto const depth_attachment = vk::AttachmentDescription{} .setFormat(depth_format) .setSamples(vk::SampleCountFlagBits::e1) .setLoadOp(vk::AttachmentLoadOp::eClear) .setStoreOp(vk::AttachmentStoreOp::eDontCare) .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare) .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare) .setInitialLayout(vk::ImageLayout::eUndefined) .setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal); auto const depth_attachment_ref = vk::AttachmentReference{} .setAttachment(1) .setLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal); bool const use_depth_attachment = depth_format != vk::Format::eUndefined; auto const subpass = vk::SubpassDescription{} .setPipelineBindPoint(vk::PipelineBindPoint::eGraphics) .setColorAttachmentCount(1) .setPColorAttachments(&color_attachment_ref) .setPDepthStencilAttachment(use_depth_attachment ? &depth_attachment_ref : nullptr); std::vector attachments{color_attachment}; if (use_depth_attachment) attachments.push_back(depth_attachment); auto const subpass_dependency = vk::SubpassDependency{} .setSrcSubpass(VK_SUBPASS_EXTERNAL) .setSrcStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput) .setSrcAccessMask({}) .setDstSubpass(0) .setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput) .setDstAccessMask(vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite) .setDependencyFlags(vk::DependencyFlagBits::eByRegion); auto const render_pass_create_info = vk::RenderPassCreateInfo{} .setAttachmentCount(attachments.size()) .setPAttachments(attachments.data()) .setSubpassCount(1) .setPSubpasses(&subpass) .setDependencyCount(1) .setPDependencies(&subpass_dependency); return ManagedResource{ vulkan.device().createRenderPass(render_pass_create_info), [vptr=&vulkan] (auto const& rp) { vptr->device().destroyRenderPass(rp); }}; } vkmark-2017.08+git20220909/src/vkutil/render_pass_builder.h000066400000000000000000000025431441550741700231020ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" class VulkanState; namespace vkutil { class RenderPassBuilder { public: RenderPassBuilder(VulkanState& vulkan); RenderPassBuilder& set_color_format(vk::Format format); RenderPassBuilder& set_depth_format(vk::Format format); RenderPassBuilder& set_color_load_op(vk::AttachmentLoadOp load_op); ManagedResource build(); private: VulkanState& vulkan; vk::Format color_format; vk::Format depth_format; vk::AttachmentLoadOp color_load_op; }; } vkmark-2017.08+git20220909/src/vkutil/semaphore_builder.cpp000066400000000000000000000023011441550741700231030ustar00rootroot00000000000000/* * Copyright © 2018 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "semaphore_builder.h" #include "vulkan_state.h" vkutil::SemaphoreBuilder::SemaphoreBuilder(VulkanState& vulkan) : vulkan{vulkan} { } ManagedResource vkutil::SemaphoreBuilder::build() { return ManagedResource{ vulkan.device().createSemaphore(vk::SemaphoreCreateInfo{}), [vptr=&vulkan] (auto const& s) { vptr->device().destroySemaphore(s); }}; } vkmark-2017.08+git20220909/src/vkutil/semaphore_builder.h000066400000000000000000000020741441550741700225570ustar00rootroot00000000000000/* * Copyright © 2018 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" class VulkanState; namespace vkutil { class SemaphoreBuilder { public: SemaphoreBuilder(VulkanState& vulkan); ManagedResource build(); private: VulkanState& vulkan; }; } vkmark-2017.08+git20220909/src/vkutil/texture.h000066400000000000000000000020341441550741700205620ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include "managed_resource.h" namespace vkutil { struct Texture { ManagedResource image; ManagedResource image_view; ManagedResource sampler; }; } vkmark-2017.08+git20220909/src/vkutil/texture_builder.cpp000066400000000000000000000114471441550741700226330ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "texture_builder.h" #include "texture.h" #include "util.h" #include "vulkan_state.h" #include "buffer_builder.h" #include "copy_buffer.h" #include "image_builder.h" #include "image_view_builder.h" #include "transition_image_layout.h" namespace { void texture_setup_image(VulkanState& vulkan, vkutil::Texture& texture, Util::Image const& image) { auto const texture_format = vk::Format::eR8G8B8A8Srgb; vk::DeviceMemory staging_buffer_memory; auto const image_extent = vk::Extent2D{ static_cast(image.width), static_cast(image.height)}; auto staging_buffer = vkutil::BufferBuilder{vulkan} .set_size(image.size) .set_usage(vk::BufferUsageFlagBits::eTransferSrc) .set_memory_properties( vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) .set_memory_out(staging_buffer_memory) .build(); auto const staging_buffer_map = vulkan.device().mapMemory( staging_buffer_memory, 0, image.size); memcpy(staging_buffer_map, image.data, image.size); vulkan.device().unmapMemory(staging_buffer_memory); texture.image = vkutil::ImageBuilder{vulkan} .set_extent(image_extent) .set_format(texture_format) .set_tiling(vk::ImageTiling::eOptimal) .set_usage(vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled) .set_memory_properties(vk::MemoryPropertyFlagBits::eDeviceLocal) .set_initial_layout(vk::ImageLayout::ePreinitialized) .build(); vkutil::transition_image_layout( vulkan, texture.image, vk::ImageLayout::ePreinitialized, vk::ImageLayout::eTransferDstOptimal, vk::ImageAspectFlagBits::eColor); vkutil::copy_buffer_to_image(vulkan, staging_buffer, texture.image, image_extent); vkutil::transition_image_layout( vulkan, texture.image, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, vk::ImageAspectFlagBits::eColor); texture.image_view = vkutil::ImageViewBuilder{vulkan} .set_image(texture.image) .set_format(texture_format) .set_aspect_mask(vk::ImageAspectFlagBits::eColor) .build(); } void texture_setup_sampler(VulkanState& vulkan, vkutil::Texture& texture, vk::Filter filter, float anisotropy) { auto const sampler_create_info = vk::SamplerCreateInfo{} .setMagFilter(filter) .setMinFilter(filter) .setAddressModeU(vk::SamplerAddressMode::eRepeat) .setAddressModeV(vk::SamplerAddressMode::eRepeat) .setAddressModeW(vk::SamplerAddressMode::eRepeat) .setAnisotropyEnable(anisotropy > 0.0f) .setMaxAnisotropy(anisotropy) .setUnnormalizedCoordinates(false) .setCompareEnable(false) .setMinLod(0.0f) .setMaxLod(0.25f) .setMipmapMode(vk::SamplerMipmapMode::eNearest); texture.sampler = ManagedResource{ vulkan.device().createSampler(sampler_create_info), [vptr=&vulkan] (auto const& s) { vptr->device().destroySampler(s); }}; } } vkutil::TextureBuilder::TextureBuilder(VulkanState& vulkan) : vulkan{vulkan}, filter{vk::Filter::eNearest}, anisotropy{0.0f} { } vkutil::TextureBuilder& vkutil::TextureBuilder::set_file(std::string const& file_) { file = file_; return *this; } vkutil::TextureBuilder& vkutil::TextureBuilder::set_filter(vk::Filter filter_) { filter = filter_; return *this; } vkutil::TextureBuilder& vkutil::TextureBuilder::set_anisotropy(float a) { anisotropy = a; return *this; } vkutil::Texture vkutil::TextureBuilder::build() { Texture texture; auto const image = Util::read_image_file(file); texture_setup_image(vulkan, texture, image); texture_setup_sampler(vulkan, texture, filter, anisotropy); return texture; } vkmark-2017.08+git20220909/src/vkutil/texture_builder.h000066400000000000000000000024121441550741700222700ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include class VulkanState; namespace vkutil { struct Texture; class TextureBuilder { public: TextureBuilder(VulkanState& vulkan); TextureBuilder& set_file(std::string const& file); TextureBuilder& set_filter(vk::Filter filter); TextureBuilder& set_anisotropy(float anisotropy); Texture build(); private: VulkanState& vulkan; std::string file; vk::Filter filter; float anisotropy; }; } vkmark-2017.08+git20220909/src/vkutil/transition_image_layout.cpp000066400000000000000000000056011441550741700243510ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "transition_image_layout.h" #include "one_time_command_buffer.h" namespace { vk::AccessFlags access_mask_for_layout(vk::ImageLayout layout) { vk::AccessFlags ret; switch (layout) { case vk::ImageLayout::eDepthStencilAttachmentOptimal: ret = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite; break; default: break; }; return ret; } vk::PipelineStageFlags pipeline_stage_flags_for_layout(vk::ImageLayout layout) { vk::PipelineStageFlags ret; switch (layout) { case vk::ImageLayout::eDepthStencilAttachmentOptimal: ret = vk::PipelineStageFlagBits::eEarlyFragmentTests; break; case vk::ImageLayout::eTransferDstOptimal: ret = vk::PipelineStageFlagBits::eTransfer; break; default: ret = vk::PipelineStageFlagBits::eTopOfPipe; break; }; return ret; } } void vkutil::transition_image_layout( VulkanState& vulkan, vk::Image image, vk::ImageLayout old_layout, vk::ImageLayout new_layout, vk::ImageAspectFlags aspect_mask) { auto const image_subresource_range = vk::ImageSubresourceRange{} .setAspectMask(aspect_mask) .setBaseMipLevel(0) .setLevelCount(1) .setBaseArrayLayer(0) .setLayerCount(1); auto const image_memory_barrier = vk::ImageMemoryBarrier{} .setImage(image) .setOldLayout(old_layout) .setNewLayout(new_layout) .setSrcAccessMask(access_mask_for_layout(old_layout)) .setDstAccessMask(access_mask_for_layout(new_layout)) .setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) .setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) .setSubresourceRange(image_subresource_range); OneTimeCommandBuffer otcb{vulkan}; otcb.command_buffer().pipelineBarrier( pipeline_stage_flags_for_layout(old_layout), pipeline_stage_flags_for_layout(new_layout), {}, {}, {}, image_memory_barrier); otcb.submit(); } vkmark-2017.08+git20220909/src/vkutil/transition_image_layout.h000066400000000000000000000020611441550741700240130ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include class VulkanState; namespace vkutil { void transition_image_layout( VulkanState& vulkan, vk::Image image, vk::ImageLayout old_layout, vk::ImageLayout new_layout, vk::ImageAspectFlags aspect_mask); } vkmark-2017.08+git20220909/src/vkutil/vkutil.h000066400000000000000000000023361441550741700204050ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "buffer_builder.h" #include "copy_buffer.h" #include "descriptor_set_builder.h" #include "find_matching_memory_type.h" #include "framebuffer_builder.h" #include "image_builder.h" #include "image_view_builder.h" #include "map_memory.h" #include "pipeline_builder.h" #include "render_pass_builder.h" #include "semaphore_builder.h" #include "texture.h" #include "texture_builder.h" #include "transition_image_layout.h" vkmark-2017.08+git20220909/src/vulkan_image.h000066400000000000000000000021371441550741700202120ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include struct VulkanImage { VulkanImage copy_with_semaphore(vk::Semaphore sem) const { return {index, image, format, extent, sem}; } uint32_t index; vk::Image image; vk::Format format; vk::Extent2D extent; vk::Semaphore semaphore; }; vkmark-2017.08+git20220909/src/vulkan_state.cpp000066400000000000000000000200521441550741700205770ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "vulkan_state.h" #include "device_uuid.h" #include "log.h" #include #include #include VulkanState::VulkanState(VulkanWSI& vulkan_wsi, ChoosePhysicalDeviceStrategy const& pd_strategy) { create_instance(vulkan_wsi); create_physical_device(vulkan_wsi, pd_strategy); create_logical_device(vulkan_wsi); create_command_pool(); } std::vector VulkanState::available_devices(VulkanWSI& vulkan_wsi) const { auto available_devices = instance().enumeratePhysicalDevices(); for (auto it_device = available_devices.begin(); it_device != available_devices.end();) { if (!vulkan_wsi.is_physical_device_supported(*it_device)) { Log::debug("Device with uuid %s is not supported by window system integration layer\n", static_cast(it_device->getProperties().pipelineCacheUUID).representation().data()); it_device = available_devices.erase(it_device); } else ++it_device; } return available_devices; } static void log_device_info(vk::PhysicalDevice const& device) { auto const props = device.getProperties(); Log::info(" Vendor ID: 0x%X\n", props.vendorID); Log::info(" Device ID: 0x%X\n", props.deviceID); Log::info(" Device Name: %s\n", static_cast(props.deviceName)); Log::info(" Driver Version: %u\n", props.driverVersion); Log::info(" Device UUID: %s\n", static_cast(props.pipelineCacheUUID).representation().data()); } void VulkanState::log_info() const { log_device_info(physical_device()); } void VulkanState::log_all_devices() const { std::vector const& physical_devices = instance().enumeratePhysicalDevices(); for (size_t i = 0; i < physical_devices.size(); ++i) { Log::info("=== Physical Device %d ===\n", i); log_device_info(physical_devices[i]); } } void VulkanState::create_instance(VulkanWSI& vulkan_wsi) { auto const app_info = vk::ApplicationInfo{} .setPApplicationName("vkmark"); std::vector enabled_extensions{vulkan_wsi.required_extensions().instance}; enabled_extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); auto const create_info = vk::InstanceCreateInfo{} .setPApplicationInfo(&app_info) .setEnabledExtensionCount(enabled_extensions.size()) .setPpEnabledExtensionNames(enabled_extensions.data()); vk_instance = ManagedResource{ vk::createInstance(create_info), [] (auto& i) { i.destroy(); }}; } void VulkanState::create_physical_device(VulkanWSI& vulkan_wsi, ChoosePhysicalDeviceStrategy const& pd_strategy) { vk_physical_device = pd_strategy(available_devices(vulkan_wsi)); } // pseudo-optional static std::pair find_queue_family_index(vk::PhysicalDevice pd, vk::QueueFlagBits queue_family_type) { auto const queue_families = pd.getQueueFamilyProperties(); for (uint32_t queue_index = 0; queue_index < queue_families.size(); ++queue_index) { // each queue family must support at least one queue if (queue_families[queue_index].queueFlags & queue_family_type) return std::make_pair(queue_index, true); } return std::make_pair(0, false); } void VulkanState::create_logical_device(VulkanWSI& vulkan_wsi) { // it would be really nice to support c++17 auto pair = find_queue_family_index(physical_device(), vk::QueueFlagBits::eGraphics); if (!pair.second) throw std::runtime_error("selected physical device does not provide graphics queue"); vk_graphics_queue_family_index = pair.first; auto const priority = 1.0f; auto queue_family_indices = vulkan_wsi.physical_device_queue_family_indices(physical_device()); if (!queue_family_indices.empty()) { Log::debug("VulkanState: Using queue family index %d for WSI operations\n", queue_family_indices.front()); } if (std::find(queue_family_indices.begin(), queue_family_indices.end(), graphics_queue_family_index()) == queue_family_indices.end()) { queue_family_indices.push_back(graphics_queue_family_index()); } std::vector queue_create_infos; for (auto index : queue_family_indices) { queue_create_infos.push_back( vk::DeviceQueueCreateInfo{} .setQueueFamilyIndex(index) .setQueueCount(1) .setPQueuePriorities(&priority)); } Log::debug("VulkanState: Using queue family index %d for rendering\n", graphics_queue_family_index()); std::vector enabled_extensions{vulkan_wsi.required_extensions().device}; auto const device_features = vk::PhysicalDeviceFeatures{} .setSamplerAnisotropy(true); auto const device_create_info = vk::DeviceCreateInfo{} .setQueueCreateInfoCount(queue_create_infos.size()) .setPQueueCreateInfos(queue_create_infos.data()) .setEnabledExtensionCount(enabled_extensions.size()) .setPpEnabledExtensionNames(enabled_extensions.data()) .setPEnabledFeatures(&device_features); vk_device = ManagedResource{ physical_device().createDevice(device_create_info), [] (auto& d) { d.destroy(); }}; vk_graphics_queue = device().getQueue(graphics_queue_family_index(), 0); } void VulkanState::create_command_pool() { auto const command_pool_create_info = vk::CommandPoolCreateInfo{} .setQueueFamilyIndex(graphics_queue_family_index()) .setFlags(vk::CommandPoolCreateFlagBits::eResetCommandBuffer); vk_command_pool = ManagedResource{ device().createCommandPool(command_pool_create_info), [this] (auto& cp) { this->device().destroyCommandPool(cp); }}; } vk::PhysicalDevice ChooseFirstSupportedStrategy::operator()(const std::vector& available_devices) { Log::debug("Trying to use first supported device\n"); for (auto const& physical_device : available_devices) { if (find_queue_family_index(physical_device, vk::QueueFlagBits::eGraphics).second) { Log::debug("First supported device chosen\n"); return physical_device; } Log::debug("Device with uuid %s skipped\n", static_cast(physical_device.getProperties().pipelineCacheUUID).representation().data() ); } throw std::runtime_error("No suitable Vulkan physical devices found"); } vk::PhysicalDevice ChooseByUUIDStrategy::operator()(const std::vector& available_devices) { Log::debug("Trying to use device with specified UUID %s\n", m_selected_device_uuid.representation().data()); for (auto const& physical_device: available_devices) { auto&& uuid = static_cast(physical_device.getProperties().pipelineCacheUUID); if (uuid == m_selected_device_uuid) { Log::debug("Device found by UUID\n"); return physical_device; } } // if device is not supported by wsi it would appear in list_all_devices but is not available here throw std::runtime_error(std::string("Device specified by uuid is not available")); } vkmark-2017.08+git20220909/src/vulkan_state.h000066400000000000000000000054351441550741700202540ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include #include "managed_resource.h" #include "vulkan_wsi.h" #include "device_uuid.h" class VulkanState { public: using ChoosePhysicalDeviceStrategy = std::function const&)>; VulkanState(VulkanWSI& vulkan_wsi, ChoosePhysicalDeviceStrategy const& pd_strategy); vk::Instance const& instance() const { return vk_instance; } vk::PhysicalDevice const& physical_device() const { return vk_physical_device; } uint32_t const& graphics_queue_family_index() const { return vk_graphics_queue_family_index; } vk::Device const& device() const { return vk_device; } vk::Queue const& graphics_queue() const { return vk_graphics_queue; } vk::CommandPool const& command_pool() const { return vk_command_pool; } void log_info() const; void log_all_devices() const; private: void create_instance(VulkanWSI& vulkan_wsi); void create_physical_device(VulkanWSI& vulkan_wsi, ChoosePhysicalDeviceStrategy const& pd_strategy); void create_logical_device(VulkanWSI& vulkan_wsi); void create_command_pool(); std::vector available_devices(VulkanWSI& vulkan_wsi) const; ManagedResource vk_instance; ManagedResource vk_device; ManagedResource vk_command_pool; vk::Queue vk_graphics_queue; vk::PhysicalDevice vk_physical_device; uint32_t vk_graphics_queue_family_index; }; class ChooseFirstSupportedStrategy { public: vk::PhysicalDevice operator()(const std::vector& available_devices); }; class ChooseByUUIDStrategy { public: ChooseByUUIDStrategy(const DeviceUUID& uuid) : m_selected_device_uuid(uuid) {} vk::PhysicalDevice operator()(const std::vector& available_devices); private: DeviceUUID m_selected_device_uuid; }; vkmark-2017.08+git20220909/src/vulkan_wsi.h000066400000000000000000000026711441550741700177350ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include namespace vk { class PhysicalDevice; } class VulkanWSI { public: virtual ~VulkanWSI() = default; struct Extensions { std::vector instance; std::vector device; }; virtual Extensions required_extensions() = 0; virtual bool is_physical_device_supported(vk::PhysicalDevice const& pd) = 0; virtual std::vector physical_device_queue_family_indices( vk::PhysicalDevice const& pd) = 0; protected: VulkanWSI() = default; VulkanWSI(VulkanWSI const&) = delete; VulkanWSI& operator=(VulkanWSI const&) = delete; }; vkmark-2017.08+git20220909/src/window_system.h000066400000000000000000000027011441550741700204600ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include class VulkanState; struct VulkanImage; class VulkanWSI; class WindowSystem { public: virtual ~WindowSystem() = default; virtual VulkanWSI& vulkan_wsi() = 0; virtual void init_vulkan(VulkanState& vulkan) = 0; virtual void deinit_vulkan() = 0; virtual VulkanImage next_vulkan_image() = 0; virtual void present_vulkan_image(VulkanImage const&) = 0; virtual std::vector vulkan_images() = 0; virtual bool should_quit() = 0; protected: WindowSystem() = default; WindowSystem(WindowSystem const&) = delete; WindowSystem& operator=(WindowSystem&) = delete; }; vkmark-2017.08+git20220909/src/window_system_loader.cpp000066400000000000000000000145351441550741700223510ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "window_system_loader.h" #include "window_system.h" #include "window_system_plugin.h" #include "options.h" #include "log.h" #include #include #include #include #include #include namespace { void close_lib(void* handle) { if (handle) dlclose(handle); } bool string_ends_with(std::string const& str, std::string const& end) { return str.size() > end.size() && std::equal(end.rbegin(), end.rend(), str.rbegin()); } bool has_shared_object_extension(std::string const& name) { return string_ends_with(name, ".so"); } std::vector files_in_dir(std::string dir) { std::vector files; auto const dirp = opendir(dir.c_str()); if (dirp != nullptr) { dirent const* ent; while ((ent = readdir(dirp)) != nullptr) { if (has_shared_object_extension(ent->d_name)) files.push_back(dir + "/" + ent->d_name); } closedir(dirp); } return files; } } WindowSystemLoader::WindowSystemLoader(Options& options) : options{options}, lib_handle{nullptr, close_lib} { Log::debug("WindowSystemLoader: Looking in %s for window system plugins\n", options.window_system_dir.c_str()); } WindowSystemLoader::~WindowSystemLoader() = default; WindowSystem& WindowSystemLoader::load_window_system() { if (window_system) return *window_system; auto const lib = options.window_system.empty() ? probe_for_best_window_system() : window_system_from_name(options.window_system); Log::debug("WindowSystemLoader: Selected window system plugin %s %s\n", lib.c_str(), !options.window_system.empty() ? "(user selection)" : "(best match)"); lib_handle = LibHandle{dlopen(lib.c_str(), RTLD_LAZY), close_lib}; auto const ws_create = reinterpret_cast( dlsym(lib_handle.get(), "vkmark_window_system_create")); if (!ws_create) throw std::runtime_error{"Selected window system plugin doesn't provide a create function"}; window_system = ws_create(options); if (!window_system) throw std::runtime_error{"Selected window system plugin failed to create window system"}; return *window_system; } std::string WindowSystemLoader::probe_for_best_window_system() { std::string best_candidate; int best_priority = 0;; for_each_window_system( [&] (std::string const& name, void* handle) { Log::debug("WindowSystemLoader: Probing %s... ", name.c_str()); if (handle) { auto ws_probe = reinterpret_cast( dlsym(handle, "vkmark_window_system_probe")); if (ws_probe) { auto priority = ws_probe(options); if (priority > best_priority) { best_candidate = name; best_priority = priority; } auto const fmt = Log::continuation_prefix + "succeeded with priority %d\n"; Log::debug(fmt.c_str(), priority); } else { auto const fmt = Log::continuation_prefix + "failed to find probe function: %s\n"; Log::debug(fmt.c_str(), dlerror()); } } else { auto const fmt = Log::continuation_prefix + "failed to load file: %s\n"; Log::debug(fmt.c_str(), dlerror()); } }); if (!best_candidate.empty()) return best_candidate; throw std::runtime_error{"Failed to find usable window system, try using --winsys-dir"}; } void WindowSystemLoader::load_window_system_options() { for_each_window_system( [&] (std::string const& name, void* handle) { Log::debug("WindowSystemLoader: Loading options from %s... ", name.c_str()); if (handle) { auto add_options = reinterpret_cast( dlsym(handle, "vkmark_window_system_load_options")); if (add_options) { add_options(options); auto const fmt = Log::continuation_prefix + "ok\n"; Log::debug(fmt.c_str()); } else { auto const fmt = Log::continuation_prefix + "failed to find load options function: %s\n"; Log::debug(fmt.c_str(), dlerror()); } } else { auto const fmt = Log::continuation_prefix + "failed to load file: %s\n"; Log::debug(fmt.c_str(), dlerror()); } }); } void WindowSystemLoader::for_each_window_system(ForeachCallback const& callback) { auto const candidates = files_in_dir(options.window_system_dir); for (auto const& c : candidates) { auto const handle = LibHandle{dlopen(c.c_str(), RTLD_LAZY), close_lib}; callback(c, handle.get()); } } std::string WindowSystemLoader::window_system_from_name(std::string const& name) { auto const candidates = files_in_dir(options.window_system_dir); for (auto const& c : candidates) { if (string_ends_with(c, name + ".so")) return c; } throw std::runtime_error{"Failed to find specified window system '" + name + "'"}; } vkmark-2017.08+git20220909/src/window_system_loader.h000066400000000000000000000027461441550741700220170ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include #include struct Options; class WindowSystem; class WindowSystemLoader { public: WindowSystemLoader(Options& options); ~WindowSystemLoader(); void load_window_system_options(); WindowSystem& load_window_system(); private: using LibHandle = std::unique_ptr; using ForeachCallback = std::function; std::string probe_for_best_window_system(); void for_each_window_system(ForeachCallback const& callback); std::string window_system_from_name(std::string const& name); Options& options; LibHandle lib_handle; std::unique_ptr window_system; }; vkmark-2017.08+git20220909/src/window_system_plugin.h000066400000000000000000000027001441550741700220350ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include struct Options; class WindowSystem; using VkMarkWindowSystemLoadOptionsFunc = void(*)(Options&); using VkMarkWindowSystemCreateFunc = std::unique_ptr(*)(Options const&); using VkMarkWindowSystemProbeFunc = int(*)(Options const&); #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreturn-type-c-linkage" #endif extern "C" { void vkmark_window_system_load_options(Options& options); int vkmark_window_system_probe(Options const&); std::unique_ptr vkmark_window_system_create(Options const& options); } #if defined(__clang__) #pragma clang diagnostic pop #endif vkmark-2017.08+git20220909/src/ws/000077500000000000000000000000001441550741700160255ustar00rootroot00000000000000vkmark-2017.08+git20220909/src/ws/atomic_kms_window_system.cpp000066400000000000000000000221001441550741700236450ustar00rootroot00000000000000/* * Copyright © 2018 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "atomic_kms_window_system.h" #include "vulkan_image.h" #include "vulkan_state.h" #include "log.h" #include #include #include #include namespace { struct ErrnoError : std::system_error { ErrnoError(std::string const& what) : std::system_error{errno, std::system_category(), what} { } }; bool does_plane_support_crtc_index(drmModePlanePtr plane, uint32_t crtc_index) { return plane->possible_crtcs & (1 << crtc_index); } ManagedResource get_property_with_id(int drm_fd, int prop_id) { return ManagedResource{ drmModeGetProperty(drm_fd, prop_id), drmModeFreeProperty}; } bool is_plane_primary(int drm_fd, drmModePlanePtr plane) { auto const properties = ManagedResource{ drmModeObjectGetProperties(drm_fd, plane->plane_id, DRM_MODE_OBJECT_PLANE), drmModeFreeObjectProperties}; if (!properties) throw ErrnoError{"Failed to get plane properties"}; for (auto i = 0u; i < properties->count_props; ++i) { auto const property = get_property_with_id(drm_fd, properties->props[i]); if (!property) throw ErrnoError{"Failed to get plane property"}; if (!strcmp(property->name, "type") && properties->prop_values[i] == DRM_PLANE_TYPE_PRIMARY) { return true; } } return false; } ManagedResource get_plane_for_crtc( int drm_fd, drmModeResPtr resources, drmModeCrtcPtr crtc) { ManagedResource ret_plane; auto const crtc_index = std::distance( resources->crtcs, std::find(resources->crtcs, resources->crtcs + resources->count_crtcs, crtc->crtc_id)); auto const plane_resources = ManagedResource{ drmModeGetPlaneResources(drm_fd), drmModeFreePlaneResources}; if (!plane_resources) throw ErrnoError{"Failed to get plane resources"}; for (auto i = 0u; i < plane_resources->count_planes; ++i) { auto plane = ManagedResource{ drmModeGetPlane(drm_fd, plane_resources->planes[i]), drmModeFreePlane}; if (!plane) throw ErrnoError{"Failed to get plane"}; if (does_plane_support_crtc_index(plane, crtc_index)) { ret_plane = std::move(plane); if (is_plane_primary(drm_fd, ret_plane)) break; } } Log::debug("AtomicKMSWindowSystem: Using plane %d\n", ret_plane ? ret_plane->plane_id : -1); return ret_plane; } bool check_for_atomic_or_throw(int drm_fd) { if (drmSetClientCap(drm_fd, DRM_CLIENT_CAP_ATOMIC, 1) < 0) throw std::runtime_error{"Atomic not supported"}; return true; } ManagedResource get_object_properties( int drm_fd, int obj_id, int obj_type) { return ManagedResource{ drmModeObjectGetProperties(drm_fd, obj_id, obj_type), drmModeFreeObjectProperties}; } } PropertyIds::PropertyIds( int drm_fd, drmModeCrtcPtr crtc_ptr, drmModeConnectorPtr connector_ptr, drmModePlanePtr plane_ptr) { auto const crtc_properties = get_object_properties( drm_fd, crtc_ptr->crtc_id, DRM_MODE_OBJECT_CRTC); auto const connector_properties = get_object_properties( drm_fd, connector_ptr->connector_id, DRM_MODE_OBJECT_CONNECTOR); auto const plane_properties = get_object_properties( drm_fd, plane_ptr->plane_id, DRM_MODE_OBJECT_PLANE); struct { char const* name; int* id_ptr; } const data[] { {"FB_ID", &plane.fb_id}, {"CRTC_ID", &plane.crtc_id}, {"SRC_X", &plane.src_x}, {"SRC_Y", &plane.src_y}, {"SRC_W", &plane.src_w}, {"SRC_H", &plane.src_h}, {"CRTC_X", &plane.crtc_x}, {"CRTC_Y", &plane.crtc_y}, {"CRTC_W", &plane.crtc_w}, {"CRTC_H", &plane.crtc_h} }; for (auto const& d : data) *d.id_ptr = -1; for (auto i = 0u; i < plane_properties->count_props; ++i) { auto const property = get_property_with_id( drm_fd, plane_properties->props[i]); for (auto const& d : data) { if (!strcmp(property->name, d.name)) *d.id_ptr = property->prop_id; } } crtc.mode_id = -1; crtc.active = -1; for (auto i = 0u; i < crtc_properties->count_props; ++i) { auto const property = get_property_with_id( drm_fd, crtc_properties->props[i]); if (!strcmp(property->name, "MODE_ID")) crtc.mode_id = property->prop_id; else if (!strcmp(property->name, "ACTIVE")) crtc.active = property->prop_id; } connector.crtc_id = -1; for (auto i = 0u; i < connector_properties->count_props; ++i) { auto const property = get_property_with_id( drm_fd, connector_properties->props[i]); if (!strcmp(property->name, "CRTC_ID")) connector.crtc_id = property->prop_id; } } bool AtomicKMSWindowSystem::is_supported_on(std::string const& drm_device) { auto const drm_fd = ManagedResource{ open(drm_device.c_str(), O_RDWR), [](auto fd) { if (fd >= 0) close(fd); }}; return drm_fd >= 0 && drmSetClientCap(drm_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0; } AtomicKMSWindowSystem::AtomicKMSWindowSystem(std::string const& drm_device) : KMSWindowSystem(drm_device), supports_atomic{check_for_atomic_or_throw(drm_fd)}, drm_plane{get_plane_for_crtc(drm_fd, drm_resources, drm_crtc)}, property_ids{drm_fd, drm_crtc, drm_connector, drm_plane} { } void AtomicKMSWindowSystem::present_vulkan_image(VulkanImage const& vulkan_image) { auto const& fb_id = drm_fbs[vulkan_image.index]; // We can't yet use the VulkanImage semaphore in the atomic KMS window system // to synchronize rendering and presentation, so just wait for the graphics // queue to finish before flipping. vulkan->graphics_queue().waitIdle(); auto const req = ManagedResource{ drmModeAtomicAlloc(), drmModeAtomicFree}; uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; ManagedResource blob_id{ 0, [this](auto b) { if (b > 0) drmModeDestroyPropertyBlob(drm_fd, b); }}; if (!has_crtc_been_set) { drmModeAtomicAddProperty(req, drm_connector->connector_id, property_ids.connector.crtc_id, drm_crtc->crtc_id); drmModeCreatePropertyBlob(drm_fd, &drm_crtc->mode, sizeof(drm_crtc->mode), &blob_id.raw); drmModeAtomicAddProperty(req, drm_crtc->crtc_id, property_ids.crtc.mode_id, blob_id); drmModeAtomicAddProperty(req, drm_crtc->crtc_id, property_ids.crtc.active, 1); flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; has_crtc_been_set = true; } auto const plane_id = drm_plane->plane_id; drmModeAtomicAddProperty(req, plane_id, property_ids.plane.fb_id, fb_id); drmModeAtomicAddProperty(req, plane_id, property_ids.plane.crtc_id, drm_crtc->crtc_id); drmModeAtomicAddProperty(req, plane_id, property_ids.plane.src_x, 0); drmModeAtomicAddProperty(req, plane_id, property_ids.plane.src_y, 0); drmModeAtomicAddProperty(req, plane_id, property_ids.plane.src_w, drm_crtc->mode.hdisplay << 16); drmModeAtomicAddProperty(req, plane_id, property_ids.plane.src_h, drm_crtc->mode.vdisplay << 16); drmModeAtomicAddProperty(req, plane_id, property_ids.plane.crtc_x, 0); drmModeAtomicAddProperty(req, plane_id, property_ids.plane.crtc_y, 0); drmModeAtomicAddProperty(req, plane_id, property_ids.plane.crtc_w, drm_crtc->mode.hdisplay); drmModeAtomicAddProperty(req, plane_id, property_ids.plane.crtc_h, drm_crtc->mode.vdisplay); auto const ret = drmModeAtomicCommit(drm_fd, req, flags, NULL); if (ret < 0) { throw std::system_error{-ret, std::system_category(), "Failed to perform atomic commit"}; } wait_for_drm_page_flip_event(); current_image_index = (current_image_index + 1) % vk_images.size(); } vkmark-2017.08+git20220909/src/ws/atomic_kms_window_system.h000066400000000000000000000034401441550741700233200ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "kms_window_system.h" #include "managed_resource.h" #include struct PropertyIds { PropertyIds(int drm_fd, drmModeCrtcPtr crtc, drmModeConnectorPtr connector, drmModePlanePtr plane); struct { int mode_id; int active; } crtc; struct { int crtc_id; } connector; struct { int fb_id; int crtc_id; int src_x; int src_y; int src_w; int src_h; int crtc_x; int crtc_y; int crtc_w; int crtc_h; } plane; }; class AtomicKMSWindowSystem : public KMSWindowSystem { public: static bool is_supported_on(std::string const& drm_device); AtomicKMSWindowSystem(std::string const& drm_device); void present_vulkan_image(VulkanImage const&) override; protected: bool const supports_atomic; ManagedResource const drm_plane; PropertyIds const property_ids; }; vkmark-2017.08+git20220909/src/ws/kms_window_system.cpp000066400000000000000000000453171441550741700223300ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "kms_window_system.h" #include "vulkan_image.h" #include "vulkan_state.h" #include "log.h" #include #include #include #include #include #include #include #include #include namespace { ManagedResource open_drm_device(std::string const& drm_device) { int drm_fd = open(drm_device.c_str(), O_RDWR); if (drm_fd < 0) { throw std::system_error{ errno, std::system_category(), "Failed to open drm device"}; } return ManagedResource{std::move(drm_fd), close}; } ManagedResource get_resources_for(int drm_fd) { return ManagedResource{ drmModeGetResources(drm_fd), drmModeFreeResources}; } ManagedResource get_connector_with_id(int drm_fd, uint32_t connector_id) { return ManagedResource{ drmModeGetConnector(drm_fd, connector_id), drmModeFreeConnector}; } ManagedResource get_encoder_with_id(int drm_fd, uint32_t encoder_id) { return ManagedResource{ drmModeGetEncoder(drm_fd, encoder_id), drmModeFreeEncoder}; } ManagedResource get_crtc_with_id(int drm_fd, uint32_t crtc_id) { return ManagedResource{ drmModeGetCrtc(drm_fd, crtc_id), drmModeFreeCrtc}; } ManagedResource get_connected_connector( int drm_fd, drmModeResPtr resources) { for (int c = 0; c < resources->count_connectors; c++) { auto connector = get_connector_with_id(drm_fd, resources->connectors[c]); if (connector->connection == DRM_MODE_CONNECTED) { Log::debug("KMSWindowSystem: Using connector %d\n", connector->connector_id); return connector; } } throw std::runtime_error{"Failed to find a connected drm connector"}; } bool is_encoder_available(int drm_fd, drmModeResPtr resources, uint32_t encoder_id) { for (int c = 0; c < resources->count_connectors; c++) { auto connector = get_connector_with_id(drm_fd, resources->connectors[c]); if (connector->encoder_id == encoder_id && connector->connection == DRM_MODE_CONNECTED) { auto encoder = get_encoder_with_id(drm_fd, connector->encoder_id); if (encoder->crtc_id) return false; } } return true; } bool is_crtc_available(int drm_fd, drmModeResPtr resources, uint32_t crtc_id) { for (int c = 0; c < resources->count_connectors; c++) { auto connector = get_connector_with_id(drm_fd, resources->connectors[c]); if (connector->encoder_id && connector->connection == DRM_MODE_CONNECTED) { auto encoder = get_encoder_with_id(drm_fd, connector->encoder_id); if (encoder->crtc_id == crtc_id) return false; } } return true; } std::vector> get_available_encoders_for_connector( int drm_fd, drmModeResPtr resources, drmModeConnectorPtr connector) { std::vector> encoders; for (int e = 0; e < connector->count_encoders; e++) { auto const encoder_id = connector->encoders[e]; if (is_encoder_available(drm_fd, resources, encoder_id)) encoders.push_back(get_encoder_with_id(drm_fd, encoder_id)); } return encoders; } bool does_encoder_support_crtc_index(drmModeEncoderPtr encoder, uint32_t crtc_index) { return encoder->possible_crtcs & (1 << crtc_index); } ManagedResource get_current_crtc_for_connector( int drm_fd, drmModeConnectorPtr connector) { if (connector->encoder_id) { auto const encoder = get_encoder_with_id(drm_fd, connector->encoder_id); if (encoder->crtc_id) return get_crtc_with_id(drm_fd, encoder->crtc_id); } return ManagedResource{ new drmModeCrtc{}, [] (auto& c) { delete c; }}; } ManagedResource get_configured_crtc_with_id( int drm_fd, drmModeConnectorPtr connector, uint32_t crtc_id) { auto crtc = get_crtc_with_id(drm_fd, crtc_id); crtc->mode = {}; // Use the preferred mode, otherwise the largest available for (int m = 0; m < connector->count_modes; ++m) { auto const& mode_info = connector->modes[m]; if (mode_info.type & DRM_MODE_TYPE_PREFERRED) { crtc->mode = mode_info; break; } auto const mode_pixels = mode_info.hdisplay * mode_info.vdisplay; if (mode_pixels > crtc->mode.hdisplay * crtc->mode.vdisplay) crtc->mode = mode_info; } Log::debug("KMSWindowSystem: Using crtc mode %dx%d%s\n", crtc->mode.hdisplay, crtc->mode.vdisplay, crtc->mode.type & DRM_MODE_TYPE_PREFERRED ? " (preferred)" : ""); return crtc; } ManagedResource get_crtc_for_connector( int drm_fd, drmModeResPtr resources, drmModeConnectorPtr connector) { if (connector->encoder_id) { auto const encoder = get_encoder_with_id(drm_fd, connector->encoder_id); if (encoder->crtc_id) { Log::debug("KMSWindowSystem: Using already attached encoder %d, crtc %d\n", encoder->encoder_id, encoder->crtc_id); return get_configured_crtc_with_id(drm_fd, connector, encoder->crtc_id); } } Log::debug("KMSWindowSystem: No crtc/encoder attached to connector, " "trying to attach\n"); auto const available_encoders = get_available_encoders_for_connector( drm_fd, resources, connector); for (int c = 0; c < resources->count_crtcs; c++) { auto const crtc_id = resources->crtcs[c]; Log::debug("KMSWindowSystem: Trying crtc %d\n", crtc_id); if (is_crtc_available(drm_fd, resources, crtc_id)) { for (auto const& encoder : available_encoders) { if (does_encoder_support_crtc_index(encoder, c)) { return get_configured_crtc_with_id(drm_fd, connector, crtc_id); } } } } throw std::runtime_error{"Failed to get usable crtc"}; } ManagedResource create_gbm_device(int drm_fd) { auto gbm_raw = gbm_create_device(drm_fd); if (!gbm_raw) throw std::runtime_error{"Failed to create gbm device"}; return ManagedResource{std::move(gbm_raw), gbm_device_destroy}; } ManagedResource open_active_vt() { auto fd = open("/dev/tty0", O_RDONLY); if (fd < 0) throw std::runtime_error{"Failed to open active VT"}; return ManagedResource{std::move(fd), close}; } void page_flip_handler(int, unsigned int, unsigned int, unsigned int, void*) { } VTState* global_vt_state = nullptr; void restore_vt(int) { if (global_vt_state) global_vt_state->restore(); } uint32_t find_memory_type_index(vk::PhysicalDevice const& physical_device, vk::MemoryRequirements const& requirements, vk::MemoryPropertyFlags flags) { auto const properties = physical_device.getMemoryProperties(); for (uint32_t i = 0; i < properties.memoryTypeCount; i++) { if ((requirements.memoryTypeBits & (1 << i)) && (properties.memoryTypes[i].propertyFlags & flags) == flags) { return i; } } throw std::runtime_error{"Coudn't find matching memory type"}; } } VTState::VTState() : vt_fd{open_active_vt()} { if (ioctl(vt_fd, VT_GETMODE, &prev_vt_mode) < 0) { throw std::system_error{ errno, std::system_category(), "Failed to get VT control mode"}; } vt_mode const vtm{VT_PROCESS, 0, 0, 0, 0}; if (ioctl(vt_fd, VT_SETMODE, &vtm) < 0) { throw std::system_error{ errno, std::system_category(), "Failed to set VT process control mode"}; } global_vt_state = this; struct sigaction sa{}; sa.sa_handler = restore_vt; sigaction(SIGSEGV, &sa, nullptr); sigaction(SIGABRT, &sa, nullptr); } void VTState::restore() const { if (prev_vt_mode.mode == VT_AUTO) ioctl(vt_fd, VT_SETMODE, &prev_vt_mode); } VTState::~VTState() { restore(); struct sigaction sa{}; sa.sa_handler = SIG_DFL; sigaction(SIGSEGV, &sa, nullptr); sigaction(SIGABRT, &sa, nullptr); global_vt_state = nullptr; } KMSWindowSystem::KMSWindowSystem(std::string const& drm_device) : drm_fd{open_drm_device(drm_device)}, drm_resources{get_resources_for(drm_fd)}, drm_connector{get_connected_connector(drm_fd, drm_resources)}, drm_prev_crtc{get_current_crtc_for_connector(drm_fd, drm_connector)}, drm_crtc{get_crtc_for_connector(drm_fd, drm_resources, drm_connector)}, gbm{create_gbm_device(drm_fd)}, vk_extent{drm_crtc->mode.hdisplay, drm_crtc->mode.vdisplay}, vulkan{nullptr}, vk_image_format{vk::Format::eUndefined}, current_image_index{0}, has_crtc_been_set{false} { } KMSWindowSystem::~KMSWindowSystem() { drmModeSetCrtc( drm_fd, drm_prev_crtc->crtc_id, drm_prev_crtc->buffer_id, drm_prev_crtc->x, drm_prev_crtc->y, &drm_connector->connector_id, 1, &drm_prev_crtc->mode); } VulkanWSI& KMSWindowSystem::vulkan_wsi() { return *this; } void KMSWindowSystem::init_vulkan(VulkanState& vulkan_) { vulkan = &vulkan_; vk_image_format = vk::Format::eB8G8R8A8Srgb; create_gbm_bos(); create_drm_fbs(); create_vk_images(); } void KMSWindowSystem::deinit_vulkan() { vulkan->device().waitIdle(); vk_images.clear(); drm_fbs.clear(); gbm_bos.clear(); } VulkanImage KMSWindowSystem::next_vulkan_image() { return {current_image_index, vk_images[current_image_index], vk_image_format, vk_extent, nullptr}; } void KMSWindowSystem::present_vulkan_image(VulkanImage const& vulkan_image) { auto const& fb = drm_fbs[vulkan_image.index]; // We can't use the VulkanImage semaphore in the KMS window system to // synchronize rendering and presentation, so just wait for the graphics // queue to finish before flipping. vulkan->graphics_queue().waitIdle(); if (!has_crtc_been_set) { auto const ret = drmModeSetCrtc( drm_fd, drm_crtc->crtc_id, fb, 0, 0, &drm_connector->connector_id, 1, &drm_crtc->mode); if (ret < 0) throw std::system_error{-ret, std::system_category(), "Failed to set crtc"}; has_crtc_been_set = true; } drmModePageFlip(drm_fd, drm_crtc->crtc_id, fb, DRM_MODE_PAGE_FLIP_EVENT, nullptr); wait_for_drm_page_flip_event(); current_image_index = (current_image_index + 1) % vk_images.size(); } std::vector KMSWindowSystem::vulkan_images() { std::vector vulkan_images; for (uint32_t i = 0; i < vk_images.size(); ++i) vulkan_images.push_back({i, vk_images[i], vk_image_format, vk_extent, {}}); return vulkan_images; } bool KMSWindowSystem::should_quit() { return false; } void KMSWindowSystem::create_gbm_bos() { for (int i = 0; i < 2; ++i) { auto bo_raw = gbm_bo_create( gbm, vk_extent.width, vk_extent.height, GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); if (!bo_raw) throw std::runtime_error{"Failed to create gbm bo"}; gbm_bos.push_back( ManagedResource{std::move(bo_raw), gbm_bo_destroy}); } } void KMSWindowSystem::create_drm_fbs() { for (auto const& gbm_bo : gbm_bos) { uint32_t fb = 0; uint32_t handles[4] = {0}; uint32_t strides[4] = {0}; uint32_t offsets[4] = {0}; for (auto i = 0; i < gbm_bo_get_plane_count(gbm_bo); i++) { handles[i] = gbm_bo_get_handle_for_plane(gbm_bo, i).u32; offsets[i] = gbm_bo_get_offset(gbm_bo, i); strides[i] = gbm_bo_get_stride_for_plane(gbm_bo, i); } auto const ret = drmModeAddFB2( drm_fd, vk_extent.width, vk_extent.height, gbm_bo_get_format(gbm_bo), handles, strides, offsets, &fb, 0); if (ret < 0) throw std::system_error{-ret, std::system_category(), "Failed to add drm fb"}; drm_fbs.push_back( ManagedResource{ std::move(fb), [this] (auto& fb) { drmModeRmFB(drm_fd, fb); }}); } } void KMSWindowSystem::create_vk_images() { bool warned_mod_invalid = false; for (auto const& gbm_bo : gbm_bos) { vk::SubresourceLayout layouts[4] = {0}; uint32_t num_planes = gbm_bo_get_plane_count(gbm_bo); auto const fd = ManagedResource{gbm_bo_get_fd(gbm_bo), close}; uint64_t modifier = gbm_bo_get_modifier(gbm_bo); if (modifier == DRM_FORMAT_MOD_INVALID && !warned_mod_invalid) { Log::warning("KMSWindowSystem: Using VK_IMAGE_TILING_OPTIMAL for " "dmabuf with invalid modifier, but this is not " "guaranteed to work.\n"); warned_mod_invalid = true; } for (uint32_t i = 0; i < num_planes; i++) { layouts[i].offset = gbm_bo_get_offset(gbm_bo, i); layouts[i].rowPitch = gbm_bo_get_stride_for_plane(gbm_bo, i); } auto const modifier_info = vk::ImageDrmFormatModifierExplicitCreateInfoEXT{} .setDrmFormatModifier(modifier) .setDrmFormatModifierPlaneCount(num_planes) .setPPlaneLayouts(layouts); auto const external_memory_create_info = vk::ExternalMemoryImageCreateInfoKHR{} .setHandleTypes(vk::ExternalMemoryHandleTypeFlagBitsKHR::eDmaBufEXT) .setPNext(modifier != DRM_FORMAT_MOD_INVALID ? &modifier_info : nullptr); auto const image_create_info = vk::ImageCreateInfo{} .setPNext(&external_memory_create_info) .setImageType(vk::ImageType::e2D) .setFormat(vk_image_format) .setExtent({vk_extent.width, vk_extent.height, 1}) .setMipLevels(1) .setArrayLayers(1) .setSamples(vk::SampleCountFlagBits::e1) .setTiling(modifier != DRM_FORMAT_MOD_INVALID ? vk::ImageTiling::eDrmFormatModifierEXT : vk::ImageTiling::eOptimal) .setUsage(vk::ImageUsageFlagBits::eColorAttachment) .setSharingMode(vk::SharingMode::eExclusive) .setInitialLayout(vk::ImageLayout::eUndefined); auto vk_image = ManagedResource{ vulkan->device().createImage(image_create_info), [vptr=vulkan] (auto const& i) { vptr->device().destroyImage(i); }}; auto const requirements = vulkan->device().getImageMemoryRequirements(vk_image); uint32_t index = find_memory_type_index(vulkan->physical_device(), requirements, vk::MemoryPropertyFlagBits::eDeviceLocal); auto const import_memory_fd_info = vk::ImportMemoryFdInfoKHR{} .setHandleType(vk::ExternalMemoryHandleTypeFlagBitsKHR::eDmaBufEXT) .setFd(fd); auto const dedicated_allocate_info = vk::MemoryDedicatedAllocateInfoKHR{} .setPNext(&import_memory_fd_info) .setImage(vk_image); auto const memory_allocate_info = vk::MemoryAllocateInfo{} .setPNext(&dedicated_allocate_info) .setAllocationSize(requirements.size) .setMemoryTypeIndex((uint32_t)index); auto device_memory = ManagedResource{ vulkan->device().allocateMemory(memory_allocate_info), [vptr=vulkan] (auto const& m) { vptr->device().freeMemory(m); }}; vulkan->device().bindImageMemory(vk_image, device_memory, 0); vk_images.push_back( ManagedResource{ vk_image.steal(), [vptr=vulkan, mem=device_memory.steal()] (auto const& image) { vptr->device().destroyImage(image); vptr->device().freeMemory(mem); }}); } } void KMSWindowSystem::wait_for_drm_page_flip_event() { static int constexpr drm_event_context_version = 2; static drmEventContext event_context = { drm_event_context_version, nullptr, page_flip_handler}; pollfd pfd{drm_fd, POLLIN, 0}; while (true) { auto const ret = poll(&pfd, 1, 1000); if (ret < 0) { if (errno == EINTR) continue; throw std::system_error{ errno, std::system_category(), "Failed while polling for pages flip event"}; } if (pfd.revents & POLLIN) { drmHandleEvent(drm_fd, &event_context); break; } } } VulkanWSI::Extensions KMSWindowSystem::required_extensions() { return {{}, {VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME, VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME}}; } bool KMSWindowSystem::is_physical_device_supported(vk::PhysicalDevice const& physdev) { auto const props = physdev.enumerateDeviceExtensionProperties(); auto const exts = required_extensions(); auto const extension_is_supported = [&props] (std::string const& ext) { auto const iter = std::find_if(props.begin(), props.end(), [&ext](vk::ExtensionProperties prop) { return ext == prop.extensionName; }); return iter != props.end(); }; return std::all_of(exts.device.begin(), exts.device.end(), extension_is_supported); } std::vector KMSWindowSystem::physical_device_queue_family_indices( vk::PhysicalDevice const&) { return {}; } vkmark-2017.08+git20220909/src/ws/kms_window_system.h000066400000000000000000000051601441550741700217650ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "window_system.h" #include "vulkan_wsi.h" #include "managed_resource.h" #include #include #include #include class VTState { public: VTState(); ~VTState(); void restore() const; private: ManagedResource const vt_fd; vt_mode prev_vt_mode; }; class KMSWindowSystem : public WindowSystem, public VulkanWSI { public: KMSWindowSystem(std::string const& drm_device); ~KMSWindowSystem(); VulkanWSI& vulkan_wsi() override; void init_vulkan(VulkanState& vulkan) override; void deinit_vulkan() override; VulkanImage next_vulkan_image() override; void present_vulkan_image(VulkanImage const&) override; std::vector vulkan_images() override; bool should_quit() override; // VulkanWSI Extensions required_extensions() override; bool is_physical_device_supported(vk::PhysicalDevice const& pd) override; std::vector physical_device_queue_family_indices( vk::PhysicalDevice const& pd) override; protected: void create_gbm_bos(); void create_drm_fbs(); void create_vk_images(); void wait_for_drm_page_flip_event(); ManagedResource const drm_fd; ManagedResource const drm_resources; ManagedResource const drm_connector; ManagedResource const drm_prev_crtc; ManagedResource const drm_crtc; ManagedResource const gbm; vk::Extent2D const vk_extent; VTState const vt_state; VulkanState* vulkan; vk::Format vk_image_format; std::vector> gbm_bos; std::vector> drm_fbs; std::vector> vk_images; uint32_t current_image_index; bool has_crtc_been_set; }; vkmark-2017.08+git20220909/src/ws/kms_window_system_plugin.cpp000066400000000000000000000070311441550741700236750ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "window_system_plugin.h" #include "kms_window_system.h" #include "atomic_kms_window_system.h" #include "options.h" #include "log.h" #include #include #include namespace { std::string const drm_device_opt{"kms-drm-device"}; std::string const atomic_opt{"kms-atomic"}; std::string get_drm_device_option(Options const& options) { auto const& winsys_options = options.window_system_options; std::string drm_device{"/dev/dri/card0"}; for (auto const& opt : winsys_options) { if (opt.name == drm_device_opt) drm_device = opt.value; } return drm_device; } } void vkmark_window_system_load_options(Options& options) { options.add_window_system_help( "KMS window system options (pass in --winsys-options)\n" " kms-drm-device=DEV The drm device to use (default: /dev/dri/card0)\n" " kms-atomic=auto|yes|no Whether to use atomic modesetting (default: auto)\n" ); } int vkmark_window_system_probe(Options const& options) { bool supported = false; int const drm_fd = open(get_drm_device_option(options).c_str(), O_RDWR); if (drm_fd >= 0) { if (drmSetMaster(drm_fd) >= 0) { drmDropMaster(drm_fd); supported = true; } close(drm_fd); } return supported ? 255 : 0; } std::unique_ptr vkmark_window_system_create(Options const& options) { auto const& winsys_options = options.window_system_options; std::string drm_device{"/dev/dri/card0"}; std::string atomic{"auto"}; for (auto const& opt : winsys_options) { if (opt.name == drm_device_opt) { drm_device = opt.value; } else if (opt.name == atomic_opt) { if (opt.value != "auto" && opt.value != "yes" && opt.value != "no") { Log::info("KMSWindowSystemPlugin: Ignoring unknown value '%s'" " for window system option '%s'\n", opt.value.c_str(), opt.name.c_str()); } else { atomic = opt.value; } } else { Log::info("KMSWindowSystemPlugin: Ignoring unknown window system option '%s'\n", opt.name.c_str()); } } if (atomic == "yes" || (atomic == "auto" && AtomicKMSWindowSystem::is_supported_on(drm_device))) { Log::debug("KMSWindowSystemPlugin: Using atomic modesetting\n"); return std::make_unique(drm_device); } else { Log::debug("KMSWindowSystemPlugin: Using legacy modesetting\n"); return std::make_unique(drm_device); } } vkmark-2017.08+git20220909/src/ws/native_system.h000066400000000000000000000030231441550741700210660ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "managed_resource.h" #include #include class VulkanState; class NativeSystem { public: virtual ~NativeSystem() = default; virtual std::vector instance_extensions() = 0; virtual uint32_t get_presentation_queue_family_index(vk::PhysicalDevice const& pd) = 0; virtual bool should_quit() = 0; virtual vk::Extent2D get_vk_extent() = 0; virtual ManagedResource create_vk_surface(VulkanState& vulkan) = 0; static uint32_t constexpr invalid_queue_family_index = static_cast(-1); protected: NativeSystem() = default; NativeSystem(NativeSystem const&) = delete; NativeSystem& operator=(NativeSystem const&) = delete; }; vkmark-2017.08+git20220909/src/ws/swapchain_window_system.cpp000066400000000000000000000176441441550741700235150ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "swapchain_window_system.h" #include "native_system.h" #include "vulkan_state.h" #include "vulkan_image.h" #include "log.h" #include #include #include namespace { bool is_format_srgb(vk::Format f) { return to_string(f).find("Srgb") != std::string::npos; } int format_bits(vk::Format f) { int total = 0; int current = 0; bool process_digits = false; for (auto const c : to_string(f)) { if (process_digits && std::isdigit(static_cast(c))) { current = current * 10 + c - '0'; } else { process_digits = (c == 'R' || c == 'G' || c == 'B' || c == 'A'); total += current; current = 0; } } total += current; return total; } struct SurfaceFormatInfo { SurfaceFormatInfo(vk::SurfaceFormatKHR f) : format{f}, srgb{is_format_srgb(f.format)}, bits{format_bits(f.format)} { } vk::SurfaceFormatKHR format; bool srgb; int bits; }; vk::SurfaceFormatKHR select_surface_format( std::vector const& formats) { if (formats.empty()) return {}; std::vector format_infos; for (auto const& f : formats) format_infos.emplace_back(f); std::sort( format_infos.begin(), format_infos.end(), [] (auto const& a, auto const& b) { return (a.srgb && !b.srgb) || a.bits > b.bits; }); return format_infos[0].format; } } SwapchainWindowSystem::SwapchainWindowSystem( std::unique_ptr native, vk::PresentModeKHR present_mode, vk::Format pixel_format) : native{std::move(native)}, vk_present_mode{present_mode}, vk_pixel_format{pixel_format}, vulkan{nullptr} { } VulkanWSI& SwapchainWindowSystem::vulkan_wsi() { return *this; } void SwapchainWindowSystem::init_vulkan(VulkanState& vulkan_) { vulkan = &vulkan_; vk_present_queue_family_index = native->get_presentation_queue_family_index(vulkan->physical_device()); if (vk_present_queue_family_index == NativeSystem::invalid_queue_family_index) { throw std::runtime_error{ "Physical device doesn't have a queue family that supports " "presentation on the selected window system"}; } vk_present_queue = vulkan->device().getQueue(vk_present_queue_family_index, 0); vk_extent = native->get_vk_extent(); vk_surface = native->create_vk_surface(vulkan_); vk_swapchain = create_vk_swapchain(); vk_images = vulkan->device().getSwapchainImagesKHR(vk_swapchain); Log::debug("SwapchainWindowSystem: Swapchain contains %d images\n", vk_images.size()); vk_acquire_semaphore = ManagedResource{ vulkan->device().createSemaphore(vk::SemaphoreCreateInfo()), [this] (auto& s) { vulkan->device().destroySemaphore(s); }}; } void SwapchainWindowSystem::deinit_vulkan() { vulkan->device().waitIdle(); vk_acquire_semaphore = {}; vk_swapchain = {}; vk_surface = {}; } VulkanImage SwapchainWindowSystem::next_vulkan_image() { auto const image_index = vulkan->device().acquireNextImageKHR( vk_swapchain, UINT64_MAX, vk_acquire_semaphore, nullptr).value; return {image_index, vk_images[image_index], vk_image_format, vk_extent, vk_acquire_semaphore}; } void SwapchainWindowSystem::present_vulkan_image(VulkanImage const& vulkan_image) { auto const present_info = vk::PresentInfoKHR{} .setSwapchainCount(1) .setPSwapchains(&vk_swapchain.raw) .setPImageIndices(&vulkan_image.index) .setWaitSemaphoreCount(vulkan_image.semaphore ? 1 : 0) .setPWaitSemaphores(&vulkan_image.semaphore); vk_present_queue.presentKHR(present_info); } std::vector SwapchainWindowSystem::vulkan_images() { std::vector vulkan_images; for (uint32_t i = 0; i < vk_images.size(); ++i) vulkan_images.push_back({i, vk_images[i], vk_image_format, vk_extent, {}}); return vulkan_images; } bool SwapchainWindowSystem::should_quit() { return native->should_quit(); } ManagedResource SwapchainWindowSystem::create_vk_swapchain() { auto const surface_caps = vulkan->physical_device().getSurfaceCapabilitiesKHR(vk_surface); if (!(surface_caps.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eOpaque)) { throw std::runtime_error("Opaque not supported"); } if (!vulkan->physical_device().getSurfaceSupportKHR( vk_present_queue_family_index, vk_surface)) { throw std::runtime_error("Surface not supported"); } auto const surface_formats = vulkan->physical_device().getSurfaceFormatsKHR(vk_surface); if (vk_pixel_format != vk::Format::eUndefined) vk_image_format = vk_pixel_format; else vk_image_format = select_surface_format(surface_formats).format; for (auto const& format : surface_formats) { Log::debug("SwapchainWindowSystem: Available surface format %s\n", to_string(format.format).c_str()); } Log::debug("SwapchainWindowSystem: Selected swapchain format %s\n", to_string(vk_image_format).c_str()); auto const present_modes = vulkan->physical_device().getSurfacePresentModesKHR(vk_surface); if (std::find(present_modes.begin(), present_modes.end(), vk_present_mode) == present_modes.end()) { throw std::runtime_error{ "Selected present mode " + to_string(vk_present_mode) + " is not supported by the used Vulkan physical device."}; } // Try to enable triple buffering auto min_image_count = std::max(surface_caps.minImageCount, 3u); if (surface_caps.maxImageCount > 0) min_image_count = std::min(min_image_count, surface_caps.maxImageCount); auto const swapchain_create_info = vk::SwapchainCreateInfoKHR{} .setSurface(vk_surface) .setMinImageCount(min_image_count) .setImageFormat(vk_image_format) .setImageExtent(vk_extent) .setImageArrayLayers(1) .setImageUsage(vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst) .setImageSharingMode(vk::SharingMode::eExclusive) .setQueueFamilyIndexCount(1) .setPQueueFamilyIndices(&vk_present_queue_family_index) .setPresentMode(vk_present_mode); return ManagedResource{ vulkan->device().createSwapchainKHR(swapchain_create_info), [this] (auto& s) { vulkan->device().destroySwapchainKHR(s); }}; } VulkanWSI::Extensions SwapchainWindowSystem::required_extensions() { return {native->instance_extensions(), {VK_KHR_SWAPCHAIN_EXTENSION_NAME}}; } bool SwapchainWindowSystem::is_physical_device_supported(vk::PhysicalDevice const& pd) { return native->get_presentation_queue_family_index(pd) != NativeSystem::invalid_queue_family_index; } std::vector SwapchainWindowSystem::physical_device_queue_family_indices( vk::PhysicalDevice const& pd) { return {native->get_presentation_queue_family_index(pd)}; } vkmark-2017.08+git20220909/src/ws/swapchain_window_system.h000066400000000000000000000044441441550741700231540ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #include "window_system.h" #include "vulkan_wsi.h" #include "managed_resource.h" #include #include class NativeSystem; class SwapchainWindowSystem : public WindowSystem, public VulkanWSI { public: SwapchainWindowSystem( std::unique_ptr native, vk::PresentModeKHR present_mode, vk::Format pixel_format); VulkanWSI& vulkan_wsi() override; void init_vulkan(VulkanState& vulkan) override; void deinit_vulkan() override; VulkanImage next_vulkan_image() override; void present_vulkan_image(VulkanImage const&) override; std::vector vulkan_images() override; bool should_quit() override; // VulkanWSI Extensions required_extensions() override; bool is_physical_device_supported(vk::PhysicalDevice const& pd) override; std::vector physical_device_queue_family_indices( vk::PhysicalDevice const& pd) override; private: ManagedResource create_vk_swapchain(); std::unique_ptr const native; vk::PresentModeKHR const vk_present_mode; vk::Format const vk_pixel_format; VulkanState* vulkan; uint32_t vk_present_queue_family_index; vk::Queue vk_present_queue; ManagedResource vk_surface; ManagedResource vk_swapchain; ManagedResource vk_acquire_semaphore; std::vector vk_images; vk::Format vk_image_format; vk::Extent2D vk_extent; }; vkmark-2017.08+git20220909/src/ws/wayland_native_system.cpp000066400000000000000000000272671441550741700231600ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "wayland_native_system.h" #include "vulkan_state.h" #include #include #include namespace { void handle_registry_global_remove( void* /*data*/, struct wl_registry* /*registry*/, uint32_t /*name*/) { } void handle_output_geometry( void* /*data*/, wl_output* /*wl_output*/, int32_t /*x*/, int32_t /*y*/, int32_t /*physical_width*/, int32_t /*physical_height*/, int32_t /*subpixel*/, char const* /*make*/, char const* /*model*/, int32_t /*transform*/) { } void handle_output_done(void* /*data*/, wl_output* /*wl_output*/) { } void handle_keyboard_keymap( void* /*data*/, wl_keyboard* /*wl_keyboard*/, uint32_t /*format*/, int32_t /*fd*/, uint32_t /*size*/) { } void handle_keyboard_enter( void* /*data*/, wl_keyboard* /*wl_keyboard*/, uint32_t /*serial*/, wl_surface* /*surface*/, wl_array* /*keys*/) { } void handle_keyboard_leave( void* /*data*/, wl_keyboard* /*wl_keyboard*/, uint32_t /*serial*/, wl_surface* /*surface*/) { } void handle_keyboard_modifiers( void* /*data*/, wl_keyboard* /*wl_keyboard*/, uint32_t /*serial*/, uint32_t /*mods_depressed*/, uint32_t /*mods_latched*/, uint32_t /*mods_locked*/, uint32_t /*group*/) { } void handle_keyboard_repeat_info( void* /*data*/, wl_keyboard* /*wl_keyboard*/, int32_t /*rate*/, int32_t /*delay*/) { } void handle_xdg_wm_base_ping( void* /*data*/, xdg_wm_base* xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); } void handle_xdg_surface_configure( void* /*data*/, xdg_surface* xdg_surface, uint32_t serial) { xdg_surface_ack_configure(xdg_surface, serial); } void handle_xdg_toplevel_configure( void* /*data*/, struct xdg_toplevel* /*xdg_toplevel*/, int32_t /*width*/, int32_t /*height*/, wl_array* /*states*/) { } } xdg_wm_base_listener const WaylandNativeSystem::xdg_wm_base_listener{ handle_xdg_wm_base_ping, }; xdg_toplevel_listener const WaylandNativeSystem::xdg_toplevel_listener{ handle_xdg_toplevel_configure, WaylandNativeSystem::handle_xdg_toplevel_close, }; xdg_surface_listener const WaylandNativeSystem::xdg_surface_listener{ handle_xdg_surface_configure, }; wl_seat_listener const WaylandNativeSystem::seat_listener{ WaylandNativeSystem::handle_seat_capabilities }; wl_output_listener const WaylandNativeSystem::output_listener{ handle_output_geometry, WaylandNativeSystem::handle_output_mode, handle_output_done, WaylandNativeSystem::handle_output_scale }; wl_keyboard_listener const WaylandNativeSystem::keyboard_listener{ handle_keyboard_keymap, handle_keyboard_enter, handle_keyboard_leave, WaylandNativeSystem::handle_keyboard_key, handle_keyboard_modifiers, handle_keyboard_repeat_info }; WaylandNativeSystem::WaylandNativeSystem(int width, int height) : requested_width{width}, requested_height{height}, should_quit_{false}, display_fd{0}, output_width{0}, output_height{0}, output_refresh{0}, output_scale{1} { create_native_window(); } std::vector WaylandNativeSystem::instance_extensions() { return {VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME}; } uint32_t WaylandNativeSystem::get_presentation_queue_family_index(vk::PhysicalDevice const& pd) { auto const queue_families = pd.getQueueFamilyProperties(); for (auto i = 0u; i < queue_families.size(); ++i) { if (queue_families[i].queueCount > 0 && pd.getWaylandPresentationSupportKHR(i, display)) { return i; } } return invalid_queue_family_index; } bool WaylandNativeSystem::should_quit() { while (wl_display_prepare_read(display) != 0) wl_display_dispatch_pending(display); if (wl_display_flush(display) < 0 && errno != EAGAIN) { wl_display_cancel_read(display); return should_quit_; } pollfd pfd{display_fd, POLLIN, 0}; if (poll(&pfd, 1, 0) > 0) { wl_display_read_events(display); wl_display_dispatch_pending(display); } else { wl_display_cancel_read(display); } return should_quit_; } vk::Extent2D WaylandNativeSystem::get_vk_extent() { return vk_extent; } ManagedResource WaylandNativeSystem::create_vk_surface(VulkanState& vulkan) { auto const wayland_surface_create_info = vk::WaylandSurfaceCreateInfoKHR{} .setDisplay(display) .setSurface(surface); return ManagedResource{ vulkan.instance().createWaylandSurfaceKHR(wayland_surface_create_info), [vptr=&vulkan] (vk::SurfaceKHR& s) { vptr->instance().destroySurfaceKHR(s); }}; } void WaylandNativeSystem::create_native_window() { display = ManagedResource{ wl_display_connect(nullptr), [](auto d) { wl_display_flush(d); wl_display_disconnect(d); }}; if (!display) throw std::runtime_error("Failed to connect to Wayland server"); display_fd = wl_display_get_fd(display); wl_registry_listener const registry_listener = { WaylandNativeSystem::handle_registry_global, handle_registry_global_remove }; auto const registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, this); wl_display_roundtrip(display); wl_registry_destroy(registry); surface = ManagedResource{ wl_compositor_create_surface(compositor), wl_surface_destroy}; if (!surface) throw std::runtime_error("Failed to create Wayland surface"); if (!xdg_wm_base) { throw std::runtime_error( "Failed to create Wayland xdg_surface, xdg_wm_base not supported"); } xdg_surface = ManagedResource{ xdg_wm_base_get_xdg_surface(xdg_wm_base, surface), xdg_surface_destroy}; if (!xdg_surface) throw std::runtime_error("Failed to create Wayland xdg_surface"); xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, this); xdg_toplevel = ManagedResource{ xdg_surface_get_toplevel(xdg_surface), xdg_toplevel_destroy}; if (!xdg_toplevel) throw std::runtime_error("Failed to create Wayland xdg_toplevel"); xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, this); xdg_toplevel_set_app_id(xdg_toplevel, "com.github.vkmark.vkmark"); xdg_toplevel_set_title(xdg_toplevel, "vkmark " VKMARK_VERSION_STR); if (fullscreen_requested()) { xdg_toplevel_set_fullscreen(xdg_toplevel, output); vk_extent = vk::Extent2D{ static_cast(output_width), static_cast(output_height)}; } else { vk_extent = vk::Extent2D{ static_cast(requested_width), static_cast(requested_height)}; } if (wl_proxy_get_version(reinterpret_cast(output.raw)) >= WL_OUTPUT_SCALE_SINCE_VERSION) { wl_surface_set_buffer_scale(surface, output_scale); } auto const opaque_region = ManagedResource{ wl_compositor_create_region(compositor), wl_region_destroy}; wl_region_add(opaque_region, 0, 0, vk_extent.width, vk_extent.height); wl_surface_set_opaque_region(surface, opaque_region); wl_surface_commit(surface); } bool WaylandNativeSystem::fullscreen_requested() { return requested_width == -1 && requested_height == -1; } void WaylandNativeSystem::handle_registry_global( void* data, wl_registry* registry, uint32_t id, char const* interface_cstr, uint32_t version) { auto const wws = static_cast(data); auto const interface = std::string{interface_cstr ? interface_cstr : ""}; if (interface == "wl_compositor") { auto compositor_raw = static_cast( wl_registry_bind(registry, id, &wl_compositor_interface, std::min(version, 4U))); wws->compositor = ManagedResource{ std::move(compositor_raw), wl_compositor_destroy}; } else if (interface == "xdg_wm_base") { auto xdg_wm_base_raw = static_cast( wl_registry_bind( registry, id, &xdg_wm_base_interface, std::min(version, 2U))); wws->xdg_wm_base = ManagedResource{ std::move(xdg_wm_base_raw), xdg_wm_base_destroy}; xdg_wm_base_add_listener(wws->xdg_wm_base, &xdg_wm_base_listener, wws); } else if (interface == "wl_seat") { auto seat_raw = static_cast( wl_registry_bind(registry, id, &wl_seat_interface, 1)); wws->seat = ManagedResource{ std::move(seat_raw), wl_seat_destroy}; wl_seat_add_listener(wws->seat, &seat_listener, wws); } else if (interface == "wl_output") { if (!wws->output) { auto output_raw = static_cast( wl_registry_bind(registry, id, &wl_output_interface, std::min(version, 2U))); wws->output = ManagedResource{ std::move(output_raw), wl_output_destroy}; wl_output_add_listener(wws->output, &output_listener, wws); wl_display_roundtrip(wws->display); } } } void WaylandNativeSystem::handle_xdg_toplevel_close( void* data, struct xdg_toplevel* /*xdg_toplevel*/) { auto const wws = static_cast(data); wws->should_quit_ = true; } void WaylandNativeSystem::handle_seat_capabilities( void* data, wl_seat* seat, uint32_t capabilities) { auto const wws = static_cast(data); if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && !wws->keyboard) { wws->keyboard = ManagedResource{ wl_seat_get_keyboard(seat), wl_keyboard_destroy}; wl_keyboard_add_listener(wws->keyboard, &wws->keyboard_listener, wws); } else if (!(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && wws->keyboard) { wws->keyboard = {}; } } void WaylandNativeSystem::handle_output_mode( void* data, wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { if (flags & WL_OUTPUT_MODE_CURRENT) { auto const wws = static_cast(data); wws->output_width = width; wws->output_height = height; wws->output_refresh = refresh; } } void WaylandNativeSystem::handle_output_scale( void* data, wl_output* /*output*/, int32_t factor) { auto const wws = static_cast(data); wws->output_scale = factor; } void WaylandNativeSystem::handle_keyboard_key( void* data, wl_keyboard* /*wl_keyboard*/, uint32_t /*serial*/, uint32_t /*time*/, uint32_t key, uint32_t state) { if (key == KEY_ESC && state == WL_KEYBOARD_KEY_STATE_PRESSED) { auto const wws = static_cast(data); wws->should_quit_ = true; } } vkmark-2017.08+git20220909/src/ws/wayland_native_system.h000066400000000000000000000062401441550741700226110ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #define VK_USE_PLATFORM_WAYLAND_KHR #include "native_system.h" #include "xdg-shell-client-protocol.h" #include struct Options; class WaylandNativeSystem : public NativeSystem { public: WaylandNativeSystem(int width, int height); std::vector instance_extensions() override; uint32_t get_presentation_queue_family_index(vk::PhysicalDevice const& pd) override; bool should_quit() override; vk::Extent2D get_vk_extent() override; ManagedResource create_vk_surface(VulkanState& vulkan) override; private: void create_native_window(); bool fullscreen_requested(); static void handle_registry_global( void* data, wl_registry* registry, uint32_t id, char const* interface, uint32_t version); static void handle_xdg_toplevel_close( void* data, xdg_toplevel* xdg_toplevel); static void handle_seat_capabilities( void* data, wl_seat* seat, uint32_t capabilities); static void handle_output_mode( void* data, wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh); static void handle_output_scale( void* data, wl_output* output, int32_t factor); static void handle_keyboard_key( void* data, wl_keyboard* wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); static wl_seat_listener const seat_listener; static wl_keyboard_listener const keyboard_listener; static wl_output_listener const output_listener; static struct xdg_wm_base_listener const xdg_wm_base_listener; static struct xdg_toplevel_listener const xdg_toplevel_listener; static struct xdg_surface_listener const xdg_surface_listener; int const requested_width; int const requested_height; bool should_quit_; ManagedResource display; ManagedResource compositor; ManagedResource xdg_wm_base; ManagedResource seat; ManagedResource output; ManagedResource keyboard; ManagedResource surface; ManagedResource xdg_surface; ManagedResource xdg_toplevel; int display_fd; int32_t output_width; int32_t output_height; int32_t output_refresh; int32_t output_scale; vk::Extent2D vk_extent; }; vkmark-2017.08+git20220909/src/ws/wayland_window_system_plugin.cpp000066400000000000000000000030101441550741700245330ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "window_system_plugin.h" #include "swapchain_window_system.h" #include "wayland_native_system.h" #include "options.h" #include void vkmark_window_system_load_options(Options&) { } int vkmark_window_system_probe(Options const&) { auto const display = wl_display_connect(nullptr); auto const connected = display != nullptr; if (connected) wl_display_disconnect(display); return connected ? 255 : 0; } std::unique_ptr vkmark_window_system_create(Options const& options) { return std::make_unique( std::make_unique(options.size.first, options.size.second), options.present_mode, options.pixel_format); } vkmark-2017.08+git20220909/src/ws/xcb_native_system.cpp000066400000000000000000000143271441550741700222660ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "xcb_native_system.h" #include "vulkan_state.h" #include "log.h" #include #include XcbNativeSystem::XcbNativeSystem( int width, int height, xcb_visualid_t visual_id) : requested_width{width}, requested_height{height}, connection{nullptr}, window{XCB_NONE}, visual_id{visual_id}, atom_wm_protocols{XCB_NONE}, atom_wm_delete_window{XCB_NONE} { create_native_window(); } XcbNativeSystem::~XcbNativeSystem() { xcb_unmap_window(connection, window); xcb_disconnect(connection); } std::vector XcbNativeSystem::instance_extensions() { return {VK_KHR_XCB_SURFACE_EXTENSION_NAME}; } bool XcbNativeSystem::should_quit() { bool quit = false; while (auto const event = xcb_poll_for_event(connection)) { switch (event->response_type & 0x7f) { case XCB_KEY_PRESS: { auto const key_press = reinterpret_cast(event); if (key_press->detail == 9) quit = true; break; } case XCB_CLIENT_MESSAGE: { auto const client_message = reinterpret_cast(event); if (client_message->window == window && client_message->type == atom_wm_protocols && client_message->data.data32[0] == atom_wm_delete_window) { quit = true; } break; } default: break; } free(event); } return quit; } vk::Extent2D XcbNativeSystem::get_vk_extent() { return vk_extent; } uint32_t XcbNativeSystem::get_presentation_queue_family_index(vk::PhysicalDevice const& pd) { auto const queue_families = pd.getQueueFamilyProperties(); for (auto i = 0u; i < queue_families.size(); ++i) { if (queue_families[i].queueCount > 0 && pd.getXcbPresentationSupportKHR(i, connection, visual_id)) { return i; } } return invalid_queue_family_index; } ManagedResource XcbNativeSystem::create_vk_surface(VulkanState& vulkan) { auto const xcb_surface_create_info = vk::XcbSurfaceCreateInfoKHR{} .setConnection(connection) .setWindow(window); return ManagedResource{ vulkan.instance().createXcbSurfaceKHR(xcb_surface_create_info), [vptr=&vulkan] (vk::SurfaceKHR& s) { vptr->instance().destroySurfaceKHR(s); }}; } void XcbNativeSystem::create_native_window() { static std::string const title{"vkmark " VKMARK_VERSION_STR}; connection = xcb_connect(nullptr, nullptr); if (xcb_connection_has_error(connection)) throw std::runtime_error("Failed to connect to X server"); window = xcb_generate_id(connection); uint32_t const window_values[] = { XCB_EVENT_MASK_KEY_PRESS }; auto const iter = xcb_setup_roots_iterator(xcb_get_setup(connection)); auto const screen = iter.data; if (visual_id == XCB_NONE) { visual_id = screen->root_visual; Log::debug("XcbNativeSystem: Using root visual 0x%x for window\n", visual_id); } else { Log::debug("XcbNativeSystem: Using user-specified visual 0x%x for window\n", visual_id); } if (fullscreen_requested()) { vk_extent = vk::Extent2D{ static_cast(screen->width_in_pixels), static_cast(screen->height_in_pixels)}; } else { vk_extent = vk::Extent2D{ static_cast(requested_width), static_cast(requested_height)}; } xcb_create_window( connection, XCB_COPY_FROM_PARENT, window, iter.data->root, 0, 0, vk_extent.width, vk_extent.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual_id, XCB_CW_EVENT_MASK, window_values); // Set window name xcb_icccm_set_wm_name( connection, window, atom("UTF8_STRING"), 8, title.size(), title.c_str()); if (fullscreen_requested()) { // Make window fullscreen auto const atom_fs = atom("_NET_WM_STATE_FULLSCREEN"); xcb_change_property( connection, XCB_PROP_MODE_REPLACE, window, atom("_NET_WM_STATE"), XCB_ATOM_ATOM, 32, 1, &atom_fs); } else { // Make window non-resizable xcb_size_hints_t size_hints; xcb_icccm_size_hints_set_min_size(&size_hints, vk_extent.width, vk_extent.height); xcb_icccm_size_hints_set_max_size(&size_hints, vk_extent.width, vk_extent.height); xcb_icccm_set_wm_normal_hints(connection, window, &size_hints); } // Set up window delete handling atom_wm_protocols = atom("WM_PROTOCOLS"); atom_wm_delete_window = atom("WM_DELETE_WINDOW"); xcb_icccm_set_wm_protocols( connection, window, atom_wm_protocols, 1, &atom_wm_delete_window); xcb_map_window(connection, window); xcb_flush(connection); } xcb_atom_t XcbNativeSystem::atom(std::string const& name) { auto const cookie = xcb_intern_atom(connection, 0, name.size(), name.c_str()); auto const reply = xcb_intern_atom_reply(connection, cookie, nullptr); auto const ret = reply ? reply->atom : XCB_NONE; free(reply); return ret; } bool XcbNativeSystem::fullscreen_requested() { return requested_width == -1 && requested_height == -1; } vkmark-2017.08+git20220909/src/ws/xcb_native_system.h000066400000000000000000000033371441550741700217320ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #pragma once #define VK_USE_PLATFORM_XCB_KHR #include "native_system.h" #include struct Options; class XcbNativeSystem : public NativeSystem { public: XcbNativeSystem(int width, int height, xcb_visualid_t visual_id); ~XcbNativeSystem(); std::vector instance_extensions() override; uint32_t get_presentation_queue_family_index(vk::PhysicalDevice const& pd) override; bool should_quit() override; vk::Extent2D get_vk_extent() override; ManagedResource create_vk_surface(VulkanState& vulkan) override; private: void create_native_window(); xcb_atom_t atom(std::string const& name); bool fullscreen_requested(); int const requested_width; int const requested_height; xcb_connection_t* connection; xcb_window_t window; xcb_visualid_t visual_id; xcb_atom_t atom_wm_protocols; xcb_atom_t atom_wm_delete_window; vk::Extent2D vk_extent; }; vkmark-2017.08+git20220909/src/ws/xcb_window_system_plugin.cpp000066400000000000000000000045071441550741700236640ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "window_system_plugin.h" #include "swapchain_window_system.h" #include "xcb_native_system.h" #include "options.h" #include "log.h" #include namespace { std::string const visual_id_opt{"xcb-visual-id"}; } void vkmark_window_system_load_options(Options& options) { options.add_window_system_help( "XCB window system options (pass in --winsys-options)\n" " xcb-visual-id=ID The X11 visual to use in hex (default: root)\n" ); } int vkmark_window_system_probe(Options const&) { auto const connection = xcb_connect(nullptr, nullptr); auto const has_error = xcb_connection_has_error(connection); xcb_disconnect(connection); return has_error ? 0 : 127; } std::unique_ptr vkmark_window_system_create(Options const& options) { auto const& winsys_options = options.window_system_options; xcb_visualid_t visual_id = XCB_NONE; for (auto const& opt : winsys_options) { if (opt.name == visual_id_opt) { visual_id = !opt.value.empty() ? std::stoul(opt.value, nullptr, 16) : XCB_NONE; } else { Log::info("XcbWindowSystemPlugin: Ignoring unknown window system option '%s'\n", opt.name.c_str()); } } return std::make_unique( std::make_unique( options.size.first, options.size.second, visual_id), options.present_mode, options.pixel_format); } vkmark-2017.08+git20220909/tests/000077500000000000000000000000001441550741700157475ustar00rootroot00000000000000vkmark-2017.08+git20220909/tests/benchmark_collection_test.cpp000066400000000000000000000163661441550741700236730ustar00rootroot00000000000000/* * Copyright © 2017 Collabora Ltd. * * This file is part of vkmark. * * vkmark is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 2.1 of the License, or (at your option) any later version. * * vkmark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with vkmark. If not, see . * * Authors: * Alexandros Frantzis */ #include "src/benchmark_collection.h" #include "src/scene_collection.h" #include "src/scene.h" #include "src/benchmark.h" #include "test_scene.h" #include "catch.hpp" using namespace Catch::Matchers; namespace { SceneOption const option1{"option1", "value1", ""}; SceneOption const option2{"option2", "value2", ""}; struct TestSceneWithOptions : TestScene { TestSceneWithOptions(std::string const& name) : TestScene{name, {option1, option2}} { } }; std::string benchmark_string( std::string const& name, std::string const& value1 = "", std::string const& value2 = "", std::string const& extra = "") { auto ret = name; if (!value1.empty()) ret += ":" + option1.name + "=" + value1; if (!value2.empty()) ret += ":" + option2.name + "=" + value2; ret += extra; return ret; } } SCENARIO("benchmark collection", "") { SceneCollection sc; BenchmarkCollection bc{sc}; GIVEN("A few registered scenes") { auto const option_setting_scene_name = ""; auto const scene_name_1 = TestScene::name(1); auto const scene_name_2 = TestScene::name(2); auto const scene_name_3 = TestScene::name(3); sc.register_scene(std::make_unique(option_setting_scene_name)); sc.register_scene(std::make_unique(scene_name_1)); sc.register_scene(std::make_unique(scene_name_2)); sc.register_scene(std::make_unique(scene_name_3)); WHEN("adding a benchmark for an unregistered scene") { bc.add({"unregistered"}); THEN("an invalid benchmark is returned") { auto const benchmarks = bc.benchmarks(); REQUIRE(benchmarks.size() == 1); REQUIRE_FALSE(benchmarks[0]->prepare_scene().is_valid()); } } WHEN("adding a benchmark for a registered scene") { bc.add({scene_name_1}); THEN("the benchmark is returned") { auto const benchmarks = bc.benchmarks(); REQUIRE(benchmarks.size() == 1); REQUIRE(benchmarks[0]->prepare_scene().name() == scene_name_1); } } WHEN("adding a benchmark for a registered scene with options") { bc.add({benchmark_string(scene_name_1, "1", "2")}); THEN("the benchmark with set options is returned") { auto const benchmarks = bc.benchmarks(); auto const& scene = benchmarks.at(0)->prepare_scene(); REQUIRE(benchmarks.size() == 1); REQUIRE(scene.name() == scene_name_1); REQUIRE(scene.options().at(option1.name).value == "1"); REQUIRE(scene.options().at(option2.name).value == "2"); } } WHEN("adding a benchmark with invalid options") { bc.add({benchmark_string(scene_name_1, "", "2", ":testoptinvalid=1:testoptinvalid1=bla")}); THEN("the invalid options are ignored") { auto const benchmarks = bc.benchmarks(); auto const& scene = benchmarks.at(0)->prepare_scene(); REQUIRE(benchmarks.size() == 1); REQUIRE(scene.name() == scene_name_1); REQUIRE(scene.options().at(option1.name).value == option1.value); REQUIRE(scene.options().at(option2.name).value == "2"); } } WHEN("adding a benchmark for many registered scene with options") { bc.add({ benchmark_string(scene_name_1, "1", "2"), benchmark_string(scene_name_2), benchmark_string(scene_name_1, "3", "4"), }); THEN("all benchmarks with set options are returned") { auto const benchmarks = bc.benchmarks(); REQUIRE(benchmarks.size() == 3); auto const& scene1 = benchmarks.at(0)->prepare_scene(); REQUIRE(scene1.name() == scene_name_1); REQUIRE(scene1.options().at(option1.name).value == "1"); REQUIRE(scene1.options().at(option2.name).value == "2"); auto const& scene2 = benchmarks.at(1)->prepare_scene(); REQUIRE(scene2.name() == scene_name_2); REQUIRE_THROWS(scene2.options().at(option1.name)); REQUIRE_THROWS(scene2.options().at(option2.name)); auto const& scene3 = benchmarks.at(2)->prepare_scene(); REQUIRE(scene3.name() == scene_name_1); REQUIRE(scene3.options().at(option1.name).value == "3"); REQUIRE(scene3.options().at(option2.name).value == "4"); } } WHEN("adding a benchmark for an option-setting scene") { bc.add({benchmark_string(option_setting_scene_name)}); THEN("the option-setting benchmark is returned") { auto const benchmarks = bc.benchmarks(); REQUIRE(benchmarks.size() == 1); auto const& scene1 = benchmarks.at(0)->prepare_scene(); REQUIRE(scene1.name() == option_setting_scene_name); } } } } SCENARIO("benchmark collection contains normal scenes", "") { SceneCollection sc; BenchmarkCollection bc{sc}; GIVEN("A few registered scenes including an option-setting one") { auto const scene_name_1 = TestScene::name(1); auto const option_setting_scene_name = ""; sc.register_scene(std::make_unique(scene_name_1)); sc.register_scene(std::make_unique(option_setting_scene_name)); WHEN("the benchmark collection is empty") { THEN("contains no normal scenes is reported") { REQUIRE_FALSE(bc.contains_normal_scenes()); } } WHEN("adding only option-setting scenes") { bc.add({":duration=1", ":bla=2"}); THEN("contains no normal scenes is reported") { REQUIRE_FALSE(bc.contains_normal_scenes()); } } WHEN("adding at least one normal scene") { bc.add({":duration=1", scene_name_1}); THEN("contains normal scenes is reported") { REQUIRE(bc.contains_normal_scenes()); } } } } vkmark-2017.08+git20220909/tests/catch.hpp000066400000000000000000024035741441550741700175610ustar00rootroot00000000000000/* * Catch v2.13.9 * Generated: 2022-04-12 22:37:23.260201 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED // start catch.hpp #define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MINOR 13 #define CATCH_VERSION_PATCH 9 #ifdef __clang__ # pragma clang system_header #elif defined __GNUC__ # pragma GCC system_header #endif // start catch_suppress_warnings.h #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ // Because REQUIREs trigger GCC's -Wparentheses, and because still // supported version of g++ have only buggy support for _Pragmas, // Wparentheses have to be suppressed globally. # pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic ignored "-Wpadded" #endif // end catch_suppress_warnings.h #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL # define CATCH_CONFIG_ALL_PARTS #endif // In the impl file, we want to have access to all parts of the headers // Can also be used to sanely support PCHs #if defined(CATCH_CONFIG_ALL_PARTS) # define CATCH_CONFIG_EXTERNAL_INTERFACES # if defined(CATCH_CONFIG_DISABLE_MATCHERS) # undef CATCH_CONFIG_DISABLE_MATCHERS # endif # if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) # define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER # endif #endif #if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h // See e.g.: // https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ # include # if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) # define CATCH_PLATFORM_MAC # elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) # define CATCH_PLATFORM_IPHONE # endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) # define CATCH_PLATFORM_WINDOWS #endif // end catch_platform.h #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED # define CLARA_CONFIG_MAIN # endif #endif // start catch_user_interfaces.h namespace Catch { unsigned int rngSeed(); } // end catch_user_interfaces.h // start catch_tag_alias_autoregistrar.h // start catch_common.h // start catch_compiler_capabilities.h // Detect a number of compiler features - by compiler // The following features are defined: // // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? // CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? // CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too // **************** // In general each macro has a _NO_ form // (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. #ifdef __cplusplus # if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) # define CATCH_CPP14_OR_GREATER # endif # if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define CATCH_CPP17_OR_GREATER # endif #endif // Only GCC compiler should be used in this block, so other compilers trying to // mask themselves as GCC should be ignored. #if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) #endif #if defined(__clang__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) // As of this writing, IBM XL's implementation of __builtin_constant_p has a bug // which results in calls to destructors being emitted for each temporary, // without a matching initialization. In practice, this can result in something // like `std::string::~string` being called on an uninitialized value. // // For example, this code will likely segfault under IBM XL: // ``` // REQUIRE(std::string("12") + "34" == "1234") // ``` // // Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. # if !defined(__ibmxl__) && !defined(__CUDACC__) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ # endif # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) # define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) # define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) # define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // Assume that non-Windows platforms support posix signals by default #if !defined(CATCH_PLATFORM_WINDOWS) #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS #endif //////////////////////////////////////////////////////////////////////////////// // We know some environments not to support full POSIX signals #if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS #endif #ifdef __OS400__ # define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS # define CATCH_CONFIG_COLOUR_NONE #endif //////////////////////////////////////////////////////////////////////////////// // Android somehow still does not support std::to_string #if defined(__ANDROID__) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING # define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE #endif //////////////////////////////////////////////////////////////////////////////// // Not all Windows environments support SEH properly #if defined(__MINGW32__) # define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH #endif //////////////////////////////////////////////////////////////////////////////// // PS4 #if defined(__ORBIS__) # define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE #endif //////////////////////////////////////////////////////////////////////////////// // Cygwin #ifdef __CYGWIN__ // Required for some versions of Cygwin to declare gettimeofday // see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin # define _BSD_SOURCE // some versions of cygwin (most) do not support std::to_string. Use the libstd check. // https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 # if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING # endif #endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #if defined(_MSC_VER) // Universal Windows platform does not support SEH // Or console colours (or console at all...) # if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) # define CATCH_CONFIG_COLOUR_NONE # else # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif # if !defined(__clang__) // Handle Clang masquerading for msvc // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor # if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) # define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR # endif // MSVC_TRADITIONAL // Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop` # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) # endif // __clang__ #endif // _MSC_VER #if defined(_REENTRANT) || defined(_MSC_VER) // Enable async processing, as -pthread is specified or no additional linking is required # define CATCH_INTERNAL_CONFIG_USE_ASYNC #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // Check if we are compiled with -fno-exceptions or equivalent #if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) # define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED #endif //////////////////////////////////////////////////////////////////////////////// // DJGPP #ifdef __DJGPP__ # define CATCH_INTERNAL_CONFIG_NO_WCHAR #endif // __DJGPP__ //////////////////////////////////////////////////////////////////////////////// // Embarcadero C++Build #if defined(__BORLANDC__) #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN #endif //////////////////////////////////////////////////////////////////////////////// // Use of __COUNTER__ is suppressed during code analysis in // CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly // handled by it. // Otherwise all supported compilers support COUNTER macro, // but user still might want to turn it off #if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) #define CATCH_INTERNAL_CONFIG_COUNTER #endif //////////////////////////////////////////////////////////////////////////////// // RTX is a special version of Windows that is real time. // This means that it is detected as Windows, but does not provide // the same set of capabilities as real Windows does. #if defined(UNDER_RTSS) || defined(RTX64_BUILD) #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH #define CATCH_INTERNAL_CONFIG_NO_ASYNC #define CATCH_CONFIG_COLOUR_NONE #endif #if !defined(_GLIBCXX_USE_C99_MATH_TR1) #define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER #endif // Various stdlib support checks that require __has_include #if defined(__has_include) // Check if string_view is available and usable #if __has_include() && defined(CATCH_CPP17_OR_GREATER) # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW #endif // Check if optional is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) // Check if byte is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # include # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) # define CATCH_INTERNAL_CONFIG_CPP17_BYTE # endif # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) // Check if variant is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # if defined(__clang__) && (__clang_major__ < 8) // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 // fix should be in clang 8, workaround in libstdc++ 8.2 # include # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) # define CATCH_CONFIG_NO_CPP17_VARIANT # else # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) # else # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT # endif // defined(__clang__) && (__clang_major__ < 8) # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) #endif // defined(__has_include) #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) # define CATCH_CONFIG_COUNTER #endif #if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) # define CATCH_CONFIG_WINDOWS_SEH #endif // This is set by default, because we assume that unix compilers are posix-signal-compatible by default. #if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) # define CATCH_CONFIG_POSIX_SIGNALS #endif // This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. #if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) # define CATCH_CONFIG_WCHAR #endif #if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) # define CATCH_CONFIG_CPP11_TO_STRING #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) # define CATCH_CONFIG_CPP17_OPTIONAL #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) # define CATCH_CONFIG_CPP17_STRING_VIEW #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) # define CATCH_CONFIG_CPP17_VARIANT #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) # define CATCH_CONFIG_CPP17_BYTE #endif #if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) # define CATCH_INTERNAL_CONFIG_NEW_CAPTURE #endif #if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) # define CATCH_CONFIG_NEW_CAPTURE #endif #if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) # define CATCH_CONFIG_DISABLE_EXCEPTIONS #endif #if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) # define CATCH_CONFIG_POLYFILL_ISNAN #endif #if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) # define CATCH_CONFIG_USE_ASYNC #endif #if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) # define CATCH_CONFIG_ANDROID_LOGWRITE #endif #if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) # define CATCH_CONFIG_GLOBAL_NEXTAFTER #endif // Even if we do not think the compiler has that warning, we still have // to provide a macro that can be used by the code. #if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION #endif #if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS #endif // The goal of this macro is to avoid evaluation of the arguments, but // still have the compiler warn on problems inside... #if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) #endif #if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) # undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #elif defined(__clang__) && (__clang_major__ < 5) # undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) #define CATCH_TRY if ((true)) #define CATCH_CATCH_ALL if ((false)) #define CATCH_CATCH_ANON(type) if ((false)) #else #define CATCH_TRY try #define CATCH_CATCH_ALL catch (...) #define CATCH_CATCH_ANON(type) catch (type) #endif #if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) #define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #endif // end catch_compiler_capabilities.h #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) #ifdef CATCH_CONFIG_COUNTER # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) #else # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #endif #include #include #include // We need a dummy global operator<< so we can bring it into Catch namespace later struct Catch_global_namespace_dummy {}; std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); namespace Catch { struct CaseSensitive { enum Choice { Yes, No }; }; class NonCopyable { NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; protected: NonCopyable(); virtual ~NonCopyable(); }; struct SourceLineInfo { SourceLineInfo() = delete; SourceLineInfo( char const* _file, std::size_t _line ) noexcept : file( _file ), line( _line ) {} SourceLineInfo( SourceLineInfo const& other ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo( SourceLineInfo&& ) noexcept = default; SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; bool empty() const noexcept { return file[0] == '\0'; } bool operator == ( SourceLineInfo const& other ) const noexcept; bool operator < ( SourceLineInfo const& other ) const noexcept; char const* file; std::size_t line; }; std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); // Bring in operator<< from global namespace into Catch namespace // This is necessary because the overload of operator<< above makes // lookup stop at namespace Catch using ::operator<<; // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { std::string operator+() const; }; template T const& operator + ( T const& value, StreamEndStop ) { return value; } } #define CATCH_INTERNAL_LINEINFO \ ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) // end catch_common.h namespace Catch { struct RegistrarForTagAliases { RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION // end catch_tag_alias_autoregistrar.h // start catch_test_registry.h // start catch_interfaces_testcase.h #include namespace Catch { class TestSpec; struct ITestInvoker { virtual void invoke () const = 0; virtual ~ITestInvoker(); }; class TestCase; struct IConfig; struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; bool isThrowSafe( TestCase const& testCase, IConfig const& config ); bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); } // end catch_interfaces_testcase.h // start catch_stringref.h #include #include #include #include namespace Catch { /// A non-owning string class (similar to the forthcoming std::string_view) /// Note that, because a StringRef may be a substring of another string, /// it may not be null terminated. class StringRef { public: using size_type = std::size_t; using const_iterator = const char*; private: static constexpr char const* const s_empty = ""; char const* m_start = s_empty; size_type m_size = 0; public: // construction constexpr StringRef() noexcept = default; StringRef( char const* rawChars ) noexcept; constexpr StringRef( char const* rawChars, size_type size ) noexcept : m_start( rawChars ), m_size( size ) {} StringRef( std::string const& stdString ) noexcept : m_start( stdString.c_str() ), m_size( stdString.size() ) {} explicit operator std::string() const { return std::string(m_start, m_size); } public: // operators auto operator == ( StringRef const& other ) const noexcept -> bool; auto operator != (StringRef const& other) const noexcept -> bool { return !(*this == other); } auto operator[] ( size_type index ) const noexcept -> char { assert(index < m_size); return m_start[index]; } public: // named queries constexpr auto empty() const noexcept -> bool { return m_size == 0; } constexpr auto size() const noexcept -> size_type { return m_size; } // Returns the current start pointer. If the StringRef is not // null-terminated, throws std::domain_exception auto c_str() const -> char const*; public: // substrings and searches // Returns a substring of [start, start + length). // If start + length > size(), then the substring is [start, size()). // If start > size(), then the substring is empty. auto substr( size_type start, size_type length ) const noexcept -> StringRef; // Returns the current start pointer. May not be null-terminated. auto data() const noexcept -> char const*; constexpr auto isNullTerminated() const noexcept -> bool { return m_start[m_size] == '\0'; } public: // iterators constexpr const_iterator begin() const { return m_start; } constexpr const_iterator end() const { return m_start + m_size; } }; auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { return StringRef( rawChars, size ); } } // namespace Catch constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { return Catch::StringRef( rawChars, size ); } // end catch_stringref.h // start catch_preprocessor.hpp #define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ #define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) #ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ // MSVC needs more evaluations #define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) #define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) #else #define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) #endif #define CATCH_REC_END(...) #define CATCH_REC_OUT #define CATCH_EMPTY() #define CATCH_DEFER(id) id CATCH_EMPTY() #define CATCH_REC_GET_END2() 0, CATCH_REC_END #define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 #define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 #define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT #define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) #define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) #define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) #define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) #define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) // Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, // and passes userdata as the first parameter to each invocation, // e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) #define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) #define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) #define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) #define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ #define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ #define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF #define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ #define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) #else // MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF #define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) #define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ #define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) #endif #define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ #define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) #define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else #define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) #endif #define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) #define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) #define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) #define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) #define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) #define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) #define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) #define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) #define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) #define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) #define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) #define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) #define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N #define INTERNAL_CATCH_TYPE_GEN\ template struct TypeList {};\ template\ constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ template class...> struct TemplateTypeList{};\ template class...Cs>\ constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ template\ struct append;\ template\ struct rewrap;\ template class, typename...>\ struct create;\ template class, typename>\ struct convert;\ \ template \ struct append { using type = T; };\ template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ template< template class L1, typename...E1, typename...Rest>\ struct append, TypeList, Rest...> { using type = L1; };\ \ template< template class Container, template class List, typename...elems>\ struct rewrap, List> { using type = TypeList>; };\ template< template class Container, template class List, class...Elems, typename...Elements>\ struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ \ template