diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..410b95ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,227 @@ +*.html +ads.txt + +**/_out/* +**/.vscode/* +.DS_Store +build_ebook.log +temp_ebook.md +ebook/*.pdf +ebook/*.epub + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..24ee5b1b --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/LICENSE.CC-BY-SA-40 b/LICENSE.CC-BY-SA-40 new file mode 100644 index 00000000..7028425e --- /dev/null +++ b/LICENSE.CC-BY-SA-40 @@ -0,0 +1,427 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the "Licensor". The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/LICENSE.CC0 b/LICENSE.CC0 new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/LICENSE.CC0 @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README.md b/README.md index a77c09c2..b14b7f26 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,144 @@ -This website will offer a complete overview of developing applications using the new Vulkan graphics and compute API. +Vulkan tutorial +=============== -If you have found new info that is missing on this website, please submit an issue or pull request. +This repository hosts the contents of [vulkan-tutorial.com](https://vulkan-tutorial.com). +The website itself is based on [daux.io](https://github.com/dauxio/daux.io), +which supports [GitHub flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). +The actual site runs daux.io with a custom theme and a few modifications (https://github.com/Overv/daux.io) and this is built into a [Docker image](https://hub.docker.com/r/overv/vulkan-tutorial). + +Use issues and pull requests to provide feedback related to the website. If you +have a problem with your code, then use the comments section in the related +chapter to ask a question. Please provide your operating system, graphics card, +driver version, source code, expected behaviour and actual behaviour. + +E-book +------ + +This guide is now available in e-book formats as well: + +* EPUB ([English](https://vulkan-tutorial.com/resources/vulkan_tutorial_en.epub), [French](https://vulkan-tutorial.com/resources/vulkan_tutorial_fr.epub)) +* PDF ([English](https://vulkan-tutorial.com/resources/vulkan_tutorial_en.pdf), [French](https://vulkan-tutorial.com/resources/vulkan_tutorial_fr.pdf)) + +The e-book can be built from the existing content by running: + + python3 build_ebook.py + +This script depends on the following utilities being available on the path: + +* `inkscape`: SVG to PNG conversion (tested with version 1.0.2) +* `pandoc`: Building a PDF and EPUB from the Markdown code (tested with version 2.13) + +You also need to install a LaTeX distribution for PDF generation. + +Changing code across chapters +----------------------------- + +It is sometimes necessary to change code that is reused across many chapters, +for example a function like `createBuffer`. If you make such a change, then you +should update the code files using the following steps: + +* Update any chapters that reference the modified code. +* Make a copy of the first file that uses it and modify the code there, e.g. +`base_code_fixed.cpp`. +* Create a patch using +`diff -Naur base_code.cpp base_code_fixed.cpp > patch.txt`. +* Apply the patch to the specified code file and all files in later chapters +using the `incremental_patch.sh` script. Run it like this: +`./incremental_patch.sh base_code.cpp patch.txt`. +* Clean up the `base_code_fixed.cpp` and `patch.txt` files. +* Commit. + +Rendering the tutorial +----------------------------- + +To render the tutorial (i.e. convert the markdown to html), you have two options: + +1. Serve rendered files on the fly using a web server that has php installed +2. Generate static html files that you can view locally or put on a server + +For either of these options, you'll need php and a patch'ed daux. + +### PHP + +1. Make sure [PHP](http://php.net/downloads.php) is installed (Daux is written + in PHP) + 1. Both the `php_mbstring` and `php_openssl` extensions need to be enabled + 2. The `phar.readonly` setting needs to be set to `Off` (to be able to + rebuild Daux) +2. Make sure [Composer](https://getcomposer.org/) is installed, a php dependency + manager that Daux uses + +### Clone, patch, and rebuild daux + +1. Clone [daux](https://github.com/dauxio/daux.io) + * `git clone https://github.com/dauxio/daux.io.git` +2. Make a new branch at the older revision that the VulkanTutorial patch is + against: + * `git checkout d45ccff -b vtpatch` + * Making a new branch isn't strictly necessary, as you could reset `master`, + but this keeps master intact. +3. Copy over the `daux.patch` file into the daux.io directory, make sure line + endings are UNIX style (in case you're using Windows), and apply the patch. + It should apply cleanly. + * `git am daux.patch` +4. Run composer in the daux.io directory so that it downloads the dependencies + Daux needs in order to be built + * `composer install` +5. Rebuild Daux + * `php bin/compile` (this can take a while) + * A newly made `daux.phar` will now be in your base directory + +### Using Daux to serve rendered files on the fly + +Once you've completed the above, follow the instructions on the daux site +for how to [run daux using a web server](https://github.com/dauxio/daux.io/blob/master/README.md#running-remotely). + +As a simple option considering you have php installed, you can also use php's +built in development web server if you just need to locally see what things +look like: + +1. In the `daux.io` directory, edit `global.json` so that the `docs_directory` + option points at your VulkanTutorial directory + * `"docs_directory": "../VulkanTutorial",` +2. In the `daux.io` directory, run + * ` php -S localhost:8080 index.php` +3. Type `localhost:8080` in your web browser URL bar and hit enter. You should + now see the VulkanTutorial front page. + +### Using Daux to statically generate html files + +Before we generate the static files, we need to tweak daux and the tutorial +setup to prevent it from trying to load a few outside resources (which will +stall your browser when trying to load the otherwise static page) + +1. In the `VulkanTutorial` directory, edit `config.json` and remove the + `google_analytics` line so daux doesn't try to load that. +2. In the `daux.io` directory, edit `themes/daux/config.json` and remove the + `font` line so that daux doesn't try to load an external font. +3. Rebuild daux according to the earlier instructions so it picks up the theme + changes. + +We're working on improvements so in the future the above steps won't be +necessary. + +Now with the above done, we can generate the static files. Asuming the daux.io +and VulkanTutorial directories are next to each other, go into the `daux.io` +directory and run a command similar to: +`daux generate -s ../VulkanTutorial -d ../VulkanTutorial/out`. + +`-s` tells it where to find the documentation, while `-d` tells it where to put +the generated files. + +Note: if you want to generate the docs again, delete the `out` directory first +or daux will make a new `out` directory within the existing `out` directory. + +License +------- + +The contents of this repository are licensed as [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), +unless stated otherwise. By contributing to this repository, you agree to license +your contributions to the public under that same license. + +The code listings in the `code` directory are licensed as [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/). +By contributing to that directory, you agree to license your contributions to +the public under that same public domain-like license. diff --git a/assets/Khronos-Vulkan-GDC-Mar15.pdf b/assets/Khronos-Vulkan-GDC-Mar15.pdf deleted file mode 100644 index c33ebe36..00000000 Binary files a/assets/Khronos-Vulkan-GDC-Mar15.pdf and /dev/null differ diff --git a/assets/Mantle-Programming-Guide-and-API-Reference.pdf b/assets/Mantle-Programming-Guide-and-API-Reference.pdf deleted file mode 100644 index 76c1d629..00000000 Binary files a/assets/Mantle-Programming-Guide-and-API-Reference.pdf and /dev/null differ diff --git a/assets/Valve-Vulkan-Session-GDC_Mar15.pdf b/assets/Valve-Vulkan-Session-GDC_Mar15.pdf deleted file mode 100644 index bd7cb969..00000000 Binary files a/assets/Valve-Vulkan-Session-GDC_Mar15.pdf and /dev/null differ diff --git a/assets/favicon.png b/assets/favicon.png deleted file mode 100644 index 0a6e1ffe..00000000 Binary files a/assets/favicon.png and /dev/null differ diff --git a/assets/logo.png b/assets/logo.png deleted file mode 100644 index a4fc832b..00000000 Binary files a/assets/logo.png and /dev/null differ diff --git a/assets/main.css b/assets/main.css deleted file mode 100644 index 4b37ba84..00000000 --- a/assets/main.css +++ /dev/null @@ -1,69 +0,0 @@ -header { - max-width: 700px; - - margin: auto; - margin-top: 50px; - - padding-bottom: 20px; - border-bottom: 1px solid #ccc; -} - -header img { - display: block; - - height: 70px; - - margin: auto; -} - -article { - max-width: 700px; - - margin: auto; - margin-top: 20px; - - font-family: sans-serif; - font-size: 14px; -} - -article img { - width: 700px; -} - -aside { - padding: 5px; - - border: 1px solid #7F0200; - background: #CC0600; - - color: white; - text-align: center; -} - -ul.features { - list-style-type: none; -} - -.features li { - border-left: 4px solid #CC0600; - - margin-bottom: 10px; - margin-right: 50px; - padding: 10px; -} - -h2 { - margin: 0; - font-size: 16px; -} - -a { - color: inherit; - text-decoration: none; - - border-bottom: 1px dotted black; -} - -a:hover { - border-bottom: 1px solid black; -} \ No newline at end of file diff --git a/build_ebook.py b/build_ebook.py new file mode 100755 index 00000000..112483dc --- /dev/null +++ b/build_ebook.py @@ -0,0 +1,255 @@ +"""Generate EPUB and PDF ebooks from sources.""" + +from datetime import datetime +import json +import logging +from pathlib import Path +import re +from tempfile import TemporaryDirectory +import subprocess +from dataclasses import dataclass +from subprocess import CalledProcessError +from re import Match +import shutil +import argparse +import sys +from make_parser import make_parser + +logging.basicConfig( + format="%(asctime)s %(levelname)-8s %(message)s", + level=logging.INFO, + datefmt="%Y-%m-%d %H:%M:%S", +) + + +def convert_images(images_dir: Path, converted_image_dir: Path) -> None: + """Convert all SVG images to PNGs.""" + + if not converted_image_dir.exists(): + converted_image_dir.mkdir() + + for source_file in images_dir.glob("*"): + if source_file.suffix == ".svg": + dest_file = converted_image_dir / source_file.with_suffix(".png").name + + try: + subprocess.check_output( + [ + "inkscape", + f"--export-filename={dest_file.as_posix()}", + source_file.as_posix(), + ], + stderr=subprocess.STDOUT, + ) + except FileNotFoundError: + raise RuntimeError( + f"failed to convert {source_file.name} to {dest_file.name}: " + "inkscape not installed" + ) + except CalledProcessError as e: + raise RuntimeError( + f"failed to convert {source_file.name} to {dest_file.name}: " + f"inkscape failed: {e.output.decode()}" + ) + else: + shutil.copy(source_file, converted_image_dir / source_file.name) + + return converted_image_dir + + +@dataclass +class MarkdownChapter: + title: str + depth: int + contents: str + + +def find_markdown_chapters(markdown_dir: Path) -> list[Path]: + """Find all Markdown files and interpret them as chapters.""" + + markdown_entries = list(markdown_dir.rglob("*")) + markdown_entries.sort() + + markdown_chapters = [] + + for markdown_path in markdown_entries: + # Skip privacy policy (regardless of language) + if markdown_path.name.startswith("95_"): + continue + + title = markdown_path.stem.partition("_")[-1].replace("_", " ") + depth = len(markdown_path.relative_to(markdown_dir).parts) - 1 + + markdown_chapters.append( + MarkdownChapter( + title=title, + depth=depth, + contents=markdown_path.read_text() if markdown_path.is_file() else "", + ) + ) + + return markdown_chapters + + +def generate_markdown_preface() -> str: + current_date = datetime.now().strftime("%B %Y") + + return "\n".join( + [ + "% Vulkan Tutorial", + "% Alexander Overvoorde", + f"% {current_date}", + ] + ) + + +def generate_markdown_chapter( + chapter: MarkdownChapter, converted_image_dir: Path +) -> str: + contents = f"# {chapter.title}\n\n{chapter.contents}" + + # Adjust titles based on depth of chapter itself + if chapter.depth > 0: + + def adjust_title_depth(match: Match) -> str: + return ("#" * chapter.depth) + match.group(0) + + contents = re.sub(r"#+ ", adjust_title_depth, contents) + + # Fix image links + contents = contents.replace("/images/", f"{converted_image_dir.as_posix()}/") + contents = contents.replace(".svg", ".png") + + # Fix remaining relative links + contents = contents.replace("(/code", "(https://vulkan-tutorial.com/code") + contents = contents.replace("(/resources", "(https://vulkan-tutorial.com/resources") + + # Fix chapter references + def fix_chapter_reference(match: Match) -> str: + target = match.group(1).lower().replace("_", "-").split("/")[-1] + return f"](#{target})" + + contents = re.sub(r"\]\(!([^)]+)\)", fix_chapter_reference, contents) + + return contents + + +def compile_full_markdown( + markdown_dir: Path, markdown_file: Path, converted_image_dir: Path +) -> Path: + """Combine Markdown source files into one large file.""" + + markdown_fragments = [generate_markdown_preface()] + + for chapter in find_markdown_chapters(markdown_dir): + markdown_fragments.append( + generate_markdown_chapter(chapter, converted_image_dir) + ) + + markdown_file.write_text("\n\n".join(markdown_fragments)) + + return markdown_file + + +def build_pdf(markdown_file: Path, pdf_file: Path, args: argparse.Namespace) -> Path: + """Build combined Markdown file into a PDF.""" + + try: + subprocess.check_output(["xelatex", "--version"]) + except FileNotFoundError: + raise RuntimeError(f"failed to build {pdf_file}: xelatex not installed") + + try: + keys_values = [(arg, getattr(args, arg)) for arg in vars(args)] + opts = [f"{key}={val}" for key, val in keys_values if val != ""] + pandoc_args = [x for i in opts for x in ("-V", i)] + + subprocess.check_output( + [ + "pandoc", + markdown_file.as_posix(), + "-V", + "documentclass=report" + ] + + + pandoc_args + + + [ + "-t", + "latex", + "-s", + "--toc", + "--listings", + "-H", + "ebook/listings-setup.tex", + "-o", + pdf_file.as_posix(), + "--pdf-engine=xelatex", + ] + ) + except CalledProcessError as e: + raise RuntimeError( + f"failed to build {pdf_file}: pandoc failed: {e.output.decode()}" + ) + + return pdf_file + + +def build_epub(markdown_file: Path, epub_file: Path) -> Path: + try: + subprocess.check_output( + [ + "pandoc", + markdown_file.as_posix(), + "--toc", + "-o", + epub_file.as_posix(), + "--epub-cover-image=ebook/cover.png", + ] + ) + except CalledProcessError as e: + raise RuntimeError( + f"failed to build {epub_file}: pandoc failed: {e.output.decode()}" + ) + + return epub_file + +def main() -> None: + parser = make_parser() + args = parser.parse_args(sys.argv[1:]) + + """Build ebooks.""" + with TemporaryDirectory() as raw_out_dir: + out_dir = Path(raw_out_dir) + + logging.info("converting svg images to png...") + converted_image_dir = convert_images( + Path("images"), out_dir / "converted_images" + ) + + languages = json.loads(Path("config.json").read_text())["languages"].keys() + logging.info(f"building ebooks for languages {'/'.join(languages)}") + + for lang in languages: + logging.info(f"{lang}: generating markdown...") + markdown_file = compile_full_markdown( + Path(lang), out_dir / f"{lang}.md", converted_image_dir + ) + + logging.info(f"{lang}: building pdf...") + pdf_file = build_pdf(markdown_file, out_dir / f"{lang}.pdf", args) + + logging.info(f"{lang}: building epub...") + epub_file = build_epub(markdown_file, out_dir / f"{lang}.epub") + + shutil.copy(pdf_file, f"ebook/vulkan_tutorial_{lang}.pdf") + shutil.copy(epub_file, f"ebook/vulkan_tutorial_{lang}.epub") + + logging.info("done") + + +if __name__ == "__main__": + try: + main() + except RuntimeError as e: + logging.error(str(e)) diff --git a/code/.gitignore b/code/.gitignore new file mode 100644 index 00000000..01e00f3a --- /dev/null +++ b/code/.gitignore @@ -0,0 +1 @@ +CMakeLists.txt.user diff --git a/code/00_base_code.cpp b/code/00_base_code.cpp new file mode 100644 index 00000000..e2aba60d --- /dev/null +++ b/code/00_base_code.cpp @@ -0,0 +1,60 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + glfwDestroyWindow(window); + + glfwTerminate(); + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/01_instance_creation.cpp b/code/01_instance_creation.cpp new file mode 100644 index 00000000..2bad54e7 --- /dev/null +++ b/code/01_instance_creation.cpp @@ -0,0 +1,91 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + createInfo.enabledExtensionCount = glfwExtensionCount; + createInfo.ppEnabledExtensionNames = glfwExtensions; + + createInfo.enabledLayerCount = 0; + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/02_validation_layers.cpp b/code/02_validation_layers.cpp new file mode 100644 index 00000000..8e7fac42 --- /dev/null +++ b/code/02_validation_layers.cpp @@ -0,0 +1,201 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/03_physical_device_selection.cpp b/code/03_physical_device_selection.cpp new file mode 100644 index 00000000..3ccba31b --- /dev/null +++ b/code/03_physical_device_selection.cpp @@ -0,0 +1,267 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + + bool isComplete() { + return graphicsFamily.has_value(); + } +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + pickPhysicalDevice(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.isComplete(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/04_logical_device.cpp b/code/04_logical_device.cpp new file mode 100644 index 00000000..7fe1d157 --- /dev/null +++ b/code/04_logical_device.cpp @@ -0,0 +1,310 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + + bool isComplete() { + return graphicsFamily.has_value(); + } +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + pickPhysicalDevice(); + createLogicalDevice(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value(); + queueCreateInfo.queueCount = 1; + + float queuePriority = 1.0f; + queueCreateInfo.pQueuePriorities = &queuePriority; + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.pQueueCreateInfos = &queueCreateInfo; + createInfo.queueCreateInfoCount = 1; + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = 0; + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.isComplete(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/05_window_surface.cpp b/code/05_window_surface.cpp new file mode 100644 index 00000000..2fc94016 --- /dev/null +++ b/code/05_window_surface.cpp @@ -0,0 +1,338 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { + indices.graphicsFamily.value(), + indices.presentFamily.value() + }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = 0; + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.isComplete(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/06_swap_chain_creation.cpp b/code/06_swap_chain_creation.cpp new file mode 100644 index 00000000..cbca98ad --- /dev/null +++ b/code/06_swap_chain_creation.cpp @@ -0,0 +1,496 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/07_image_views.cpp b/code/07_image_views.cpp new file mode 100644 index 00000000..26c20fac --- /dev/null +++ b/code/07_image_views.cpp @@ -0,0 +1,527 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/08_graphics_pipeline.cpp b/code/08_graphics_pipeline.cpp new file mode 100644 index 00000000..4c337f75 --- /dev/null +++ b/code/08_graphics_pipeline.cpp @@ -0,0 +1,532 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createGraphicsPipeline() { + + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/09_shader_base.frag b/code/09_shader_base.frag new file mode 100644 index 00000000..36176035 --- /dev/null +++ b/code/09_shader_base.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/code/09_shader_base.vert b/code/09_shader_base.vert new file mode 100644 index 00000000..9bd71d4d --- /dev/null +++ b/code/09_shader_base.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} diff --git a/code/09_shader_modules.cpp b/code/09_shader_modules.cpp new file mode 100644 index 00000000..9de7078c --- /dev/null +++ b/code/09_shader_modules.cpp @@ -0,0 +1,586 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/10_fixed_functions.cpp b/code/10_fixed_functions.cpp new file mode 100644 index 00000000..7abe5003 --- /dev/null +++ b/code/10_fixed_functions.cpp @@ -0,0 +1,653 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkPipelineLayout pipelineLayout; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/11_render_passes.cpp b/code/11_render_passes.cpp new file mode 100644 index 00000000..3310eb00 --- /dev/null +++ b/code/11_render_passes.cpp @@ -0,0 +1,688 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/12_graphics_pipeline_complete.cpp b/code/12_graphics_pipeline_complete.cpp new file mode 100644 index 00000000..a30f38be --- /dev/null +++ b/code/12_graphics_pipeline_complete.cpp @@ -0,0 +1,710 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/13_framebuffers.cpp b/code/13_framebuffers.cpp new file mode 100644 index 00000000..95192d66 --- /dev/null +++ b/code/13_framebuffers.cpp @@ -0,0 +1,739 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/14_command_buffers.cpp b/code/14_command_buffers.cpp new file mode 100644 index 00000000..8332b5b1 --- /dev/null +++ b/code/14_command_buffers.cpp @@ -0,0 +1,817 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + VkCommandBuffer commandBuffer; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffer(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + vkDestroyCommandPool(device, commandPool, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + void createCommandBuffer() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/15_hello_triangle.cpp b/code/15_hello_triangle.cpp new file mode 100644 index 00000000..4d9f04f3 --- /dev/null +++ b/code/15_hello_triangle.cpp @@ -0,0 +1,902 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + VkCommandBuffer commandBuffer; + + VkSemaphore imageAvailableSemaphore; + VkSemaphore renderFinishedSemaphore; + VkFence inFlightFence; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffer(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanup() { + vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); + vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); + vkDestroyFence(device, inFlightFence, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + void createCommandBuffer() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast(swapChainExtent.width); + viewport.height = static_cast(swapChainExtent.height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &inFlightFence); + + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + + vkResetCommandBuffer(commandBuffer, /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffer, imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + vkQueuePresentKHR(presentQueue, &presentInfo); + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/16_frames_in_flight.cpp b/code/16_frames_in_flight.cpp new file mode 100644 index 00000000..c5746983 --- /dev/null +++ b/code/16_frames_in_flight.cpp @@ -0,0 +1,914 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanup() { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + vkQueuePresentKHR(presentQueue, &presentInfo); + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/17_swap_chain_recreation.cpp b/code/17_swap_chain_recreation.cpp new file mode 100644 index 00000000..05854eff --- /dev/null +++ b/code/17_swap_chain_recreation.cpp @@ -0,0 +1,958 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/18_shader_vertexbuffer.frag b/code/18_shader_vertexbuffer.frag new file mode 100644 index 00000000..7c5b0e74 --- /dev/null +++ b/code/18_shader_vertexbuffer.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/code/18_shader_vertexbuffer.vert b/code/18_shader_vertexbuffer.vert new file mode 100644 index 00000000..9f27f542 --- /dev/null +++ b/code/18_shader_vertexbuffer.vert @@ -0,0 +1,11 @@ +#version 450 + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} diff --git a/code/18_vertex_input.cpp b/code/18_vertex_input.cpp new file mode 100644 index 00000000..34aae683 --- /dev/null +++ b/code/18_vertex_input.cpp @@ -0,0 +1,1002 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/19_vertex_buffer.cpp b/code/19_vertex_buffer.cpp new file mode 100644 index 00000000..89b20051 --- /dev/null +++ b/code/19_vertex_buffer.cpp @@ -0,0 +1,1058 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + void createVertexBuffer() { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(vertices[0]) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create vertex buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate vertex buffer memory!"); + } + + vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0); + + void* data; + vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferInfo.size); + vkUnmapMemory(device, vertexBufferMemory); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdDraw(commandBuffer, static_cast(vertices.size()), 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/20_staging_buffer.cpp b/code/20_staging_buffer.cpp new file mode 100644 index 00000000..ac0f9b20 --- /dev/null +++ b/code/20_staging_buffer.cpp @@ -0,0 +1,1106 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdDraw(commandBuffer, static_cast(vertices.size()), 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/21_index_buffer.cpp b/code/21_index_buffer.cpp new file mode 100644 index 00000000..23fcf264 --- /dev/null +++ b/code/21_index_buffer.cpp @@ -0,0 +1,1139 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/22_descriptor_set_layout.cpp b/code/22_descriptor_set_layout.cpp new file mode 100644 index 00000000..3d54aa6a --- /dev/null +++ b/code/22_descriptor_set_layout.cpp @@ -0,0 +1,1211 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = 1; + layoutInfo.pBindings = &uboLayoutBinding; + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/22_shader_ubo.frag b/code/22_shader_ubo.frag new file mode 100644 index 00000000..7c5b0e74 --- /dev/null +++ b/code/22_shader_ubo.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/code/22_shader_ubo.vert b/code/22_shader_ubo.vert new file mode 100644 index 00000000..5ffbb2de --- /dev/null +++ b/code/22_shader_ubo.vert @@ -0,0 +1,17 @@ +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} diff --git a/code/23_descriptor_sets.cpp b/code/23_descriptor_sets.cpp new file mode 100644 index 00000000..b690bd93 --- /dev/null +++ b/code/23_descriptor_sets.cpp @@ -0,0 +1,1269 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = 1; + layoutInfo.pBindings = &uboLayoutBinding; + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } + } + + void createDescriptorPool() { + VkDescriptorPoolSize poolSize{}; + poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSize.descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = 1; + poolInfo.pPoolSizes = &poolSize; + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkWriteDescriptorSet descriptorWrite{}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = descriptorSets[i]; + descriptorWrite.dstBinding = 0; + descriptorWrite.dstArrayElement = 0; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pBufferInfo = &bufferInfo; + + vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/24_texture_image.cpp b/code/24_texture_image.cpp new file mode 100644 index 00000000..44c15bc5 --- /dev/null +++ b/code/24_texture_image.cpp @@ -0,0 +1,1424 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage textureImage; + VkDeviceMemory textureImageMemory; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createTextureImage(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = 1; + layoutInfo.pBindings = &uboLayoutBinding; + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + throw std::invalid_argument("unsupported layout transition!"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + endSingleTimeCommands(commandBuffer); + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } + } + + void createDescriptorPool() { + VkDescriptorPoolSize poolSize{}; + poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSize.descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = 1; + poolInfo.pPoolSizes = &poolSize; + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkWriteDescriptorSet descriptorWrite{}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = descriptorSets[i]; + descriptorWrite.dstBinding = 0; + descriptorWrite.dstArrayElement = 0; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pBufferInfo = &bufferInfo; + + vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/25_sampler.cpp b/code/25_sampler.cpp new file mode 100644 index 00000000..c5f4f59d --- /dev/null +++ b/code/25_sampler.cpp @@ -0,0 +1,1465 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = 1; + layoutInfo.pBindings = &uboLayoutBinding; + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB); + } + + void createTextureSampler() { + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } + } + + VkImageView createImageView(VkImage image, VkFormat format) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + throw std::invalid_argument("unsupported layout transition!"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + endSingleTimeCommands(commandBuffer); + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } + } + + void createDescriptorPool() { + VkDescriptorPoolSize poolSize{}; + poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSize.descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = 1; + poolInfo.pPoolSizes = &poolSize; + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkWriteDescriptorSet descriptorWrite{}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = descriptorSets[i]; + descriptorWrite.dstBinding = 0; + descriptorWrite.dstArrayElement = 0; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pBufferInfo = &bufferInfo; + + vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/26_shader_textures.frag b/code/26_shader_textures.frag new file mode 100644 index 00000000..873f5410 --- /dev/null +++ b/code/26_shader_textures.frag @@ -0,0 +1,12 @@ +#version 450 + +layout(binding = 1) uniform sampler2D texSampler; + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = texture(texSampler, fragTexCoord); +} diff --git a/code/26_shader_textures.vert b/code/26_shader_textures.vert new file mode 100644 index 00000000..5510aa3f --- /dev/null +++ b/code/26_shader_textures.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} diff --git a/code/26_texture_mapping.cpp b/code/26_texture_mapping.cpp new file mode 100644 index 00000000..51168ec1 --- /dev/null +++ b/code/26_texture_mapping.cpp @@ -0,0 +1,1495 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutBinding samplerLayoutBinding{}; + samplerLayoutBinding.binding = 1; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.pImmutableSamplers = nullptr; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB); + } + + void createTextureSampler() { + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } + } + + VkImageView createImageView(VkImage image, VkFormat format) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + throw std::invalid_argument("unsupported layout transition!"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + endSingleTimeCommands(commandBuffer); + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } + } + + void createDescriptorPool() { + std::array poolSizes{}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = static_cast(poolSizes.size()); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo{}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + std::array descriptorWrites{}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/27_depth_buffering.cpp b/code/27_depth_buffering.cpp new file mode 100644 index 00000000..5e0452c2 --- /dev/null +++ b/code/27_depth_buffering.cpp @@ -0,0 +1,1575 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, + + {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4 +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage depthImage; + VkDeviceMemory depthImageMemory; + VkImageView depthImageView; + + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createFramebuffers(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createDepthResources(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentDescription depthAttachment{}; + depthAttachment.format = findDepthFormat(); + depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef{}; + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + std::array attachments = {colorAttachment, depthAttachment}; + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = static_cast(attachments.size()); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutBinding samplerLayoutBinding{}; + samplerLayoutBinding.binding = 1; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.pImmutableSamplers = nullptr; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.stencilTestEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = &depthStencil; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + std::array attachments = { + swapChainImageViews[i], + depthImageView + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = static_cast(attachments.size()); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createDepthResources() { + VkFormat depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + VkFormat findDepthFormat() { + return findSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); + } + + void createTextureSampler() { + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + throw std::invalid_argument("unsupported layout transition!"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + endSingleTimeCommands(commandBuffer); + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } + } + + void createDescriptorPool() { + std::array poolSizes{}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = static_cast(poolSizes.size()); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo{}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + std::array descriptorWrites{}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + std::array clearValues{}; + clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; + clearValues[1].depthStencil = {1.0f, 0}; + + renderPassInfo.clearValueCount = static_cast(clearValues.size()); + renderPassInfo.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/27_shader_depth.frag b/code/27_shader_depth.frag new file mode 100644 index 00000000..873f5410 --- /dev/null +++ b/code/27_shader_depth.frag @@ -0,0 +1,12 @@ +#version 450 + +layout(binding = 1) uniform sampler2D texSampler; + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = texture(texSampler, fragTexCoord); +} diff --git a/code/27_shader_depth.vert b/code/27_shader_depth.vert new file mode 100644 index 00000000..840711c3 --- /dev/null +++ b/code/27_shader_depth.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} diff --git a/code/28_model_loading.cpp b/code/28_model_loading.cpp new file mode 100644 index 00000000..0097a311 --- /dev/null +++ b/code/28_model_loading.cpp @@ -0,0 +1,1621 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define TINYOBJLOADER_IMPLEMENTATION +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } + + bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } +}; + +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } + }; +} + +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage depthImage; + VkDeviceMemory depthImageMemory; + VkImageView depthImageView; + + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + + std::vector vertices; + std::vector indices; + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createFramebuffers(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createDepthResources(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentDescription depthAttachment{}; + depthAttachment.format = findDepthFormat(); + depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef{}; + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + std::array attachments = {colorAttachment, depthAttachment}; + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = static_cast(attachments.size()); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutBinding samplerLayoutBinding{}; + samplerLayoutBinding.binding = 1; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.pImmutableSamplers = nullptr; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.stencilTestEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = &depthStencil; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + std::array attachments = { + swapChainImageViews[i], + depthImageView + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = static_cast(attachments.size()); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createDepthResources() { + VkFormat depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + VkFormat findDepthFormat() { + return findSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); + } + + void createTextureSampler() { + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + throw std::invalid_argument("unsupported layout transition!"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + endSingleTimeCommands(commandBuffer); + } + + void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } + } + + void createDescriptorPool() { + std::array poolSizes{}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = static_cast(poolSizes.size()); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo{}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + std::array descriptorWrites{}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + std::array clearValues{}; + clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; + clearValues[1].depthStencil = {1.0f, 0}; + + renderPassInfo.clearValueCount = static_cast(clearValues.size()); + renderPassInfo.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/29_mipmapping.cpp b/code/29_mipmapping.cpp new file mode 100644 index 00000000..874a8d82 --- /dev/null +++ b/code/29_mipmapping.cpp @@ -0,0 +1,1715 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define TINYOBJLOADER_IMPLEMENTATION +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } + + bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } +}; + +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } + }; +} + +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage depthImage; + VkDeviceMemory depthImageMemory; + VkImageView depthImageView; + + uint32_t mipLevels; + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + + std::vector vertices; + std::vector indices; + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createFramebuffers(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createDepthResources(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentDescription depthAttachment{}; + depthAttachment.format = findDepthFormat(); + depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef{}; + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + std::array attachments = {colorAttachment, depthAttachment}; + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = static_cast(attachments.size()); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutBinding samplerLayoutBinding{}; + samplerLayoutBinding.binding = 1; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.pImmutableSamplers = nullptr; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.stencilTestEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = &depthStencil; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + std::array attachments = { + swapChainImageViews[i], + depthImageView + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = static_cast(attachments.size()); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createDepthResources() { + VkFormat depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1); + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + VkFormat findDepthFormat() { + return findSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + //transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + // Check if image format supports linear blitting + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties); + + if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.image = image; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t i = 1; i < mipLevels; i++) { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + VkImageBlit blit{}; + blit.srcOffsets[0] = {0, 0, 0}; + blit.srcOffsets[1] = {mipWidth, mipHeight, 1}; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = i - 1; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + blit.dstOffsets[0] = {0, 0, 0}; + blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = i; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = 1; + + vkCmdBlitImage(commandBuffer, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blit, + VK_FILTER_LINEAR); + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 1) mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + endSingleTimeCommands(commandBuffer); + } + + void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels); + } + + void createTextureSampler() { + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = VK_LOD_CLAMP_NONE; + samplerInfo.mipLodBias = 0.0f; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = mipLevels; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = mipLevels; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + throw std::invalid_argument("unsupported layout transition!"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + endSingleTimeCommands(commandBuffer); + } + + void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } + } + + void createDescriptorPool() { + std::array poolSizes{}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = static_cast(poolSizes.size()); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo{}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + std::array descriptorWrites{}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + std::array clearValues{}; + clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; + clearValues[1].depthStencil = {1.0f, 0}; + + renderPassInfo.clearValueCount = static_cast(clearValues.size()); + renderPassInfo.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/30_multisampling.cpp b/code/30_multisampling.cpp new file mode 100644 index 00000000..ab6cbc3f --- /dev/null +++ b/code/30_multisampling.cpp @@ -0,0 +1,1765 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define TINYOBJLOADER_IMPLEMENTATION +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } + + bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } +}; + +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } + }; +} + +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage colorImage; + VkDeviceMemory colorImageMemory; + VkImageView colorImageView; + + VkImage depthImage; + VkDeviceMemory depthImageMemory; + VkImageView depthImageView; + + uint32_t mipLevels; + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + + std::vector vertices; + std::vector indices; + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createColorResources(); + createDepthResources(); + createFramebuffers(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + vkDestroyImageView(device, colorImageView, nullptr); + vkDestroyImage(device, colorImage, nullptr); + vkFreeMemory(device, colorImageMemory, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createColorResources(); + createDepthResources(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + msaaSamples = getMaxUsableSampleCount(); + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = msaaSamples; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentDescription depthAttachment{}; + depthAttachment.format = findDepthFormat(); + depthAttachment.samples = msaaSamples; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentDescription colorAttachmentResolve{}; + colorAttachmentResolve.format = swapChainImageFormat; + colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef{}; + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentResolveRef{}; + colorAttachmentResolveRef.attachment = 2; + colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + subpass.pResolveAttachments = &colorAttachmentResolveRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve }; + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = static_cast(attachments.size()); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutBinding samplerLayoutBinding{}; + samplerLayoutBinding.binding = 1; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.pImmutableSamplers = nullptr; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = msaaSamples; + + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.stencilTestEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = &depthStencil; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + std::array attachments = { + colorImageView, + depthImageView, + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = static_cast(attachments.size()); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createColorResources() { + VkFormat colorFormat = swapChainImageFormat; + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, colorImage, colorImageMemory); + colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); + } + + void createDepthResources() { + VkFormat depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1); + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + VkFormat findDepthFormat() { + return findSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + //transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + // Check if image format supports linear blitting + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties); + + if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.image = image; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t i = 1; i < mipLevels; i++) { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + VkImageBlit blit{}; + blit.srcOffsets[0] = {0, 0, 0}; + blit.srcOffsets[1] = {mipWidth, mipHeight, 1}; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = i - 1; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + blit.dstOffsets[0] = {0, 0, 0}; + blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = i; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = 1; + + vkCmdBlitImage(commandBuffer, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blit, + VK_FILTER_LINEAR); + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 1) mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + endSingleTimeCommands(commandBuffer); + } + + VkSampleCountFlagBits getMaxUsableSampleCount() { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); + + VkSampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; } + if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; } + if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; } + if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; } + if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; } + if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; } + + return VK_SAMPLE_COUNT_1_BIT; + } + + void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels); + } + + void createTextureSampler() { + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = VK_LOD_CLAMP_NONE; + samplerInfo.mipLodBias = 0.0f; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = mipLevels; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCountFlagBits numSamples, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = mipLevels; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = numSamples; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + throw std::invalid_argument("unsupported layout transition!"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + endSingleTimeCommands(commandBuffer); + } + + void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } + } + + void createDescriptorPool() { + std::array poolSizes{}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = static_cast(poolSizes.size()); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo{}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + std::array descriptorWrites{}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + std::array clearValues{}; + clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; + clearValues[1].depthStencil = {1.0f, 0}; + + renderPassInfo.clearValueCount = static_cast(clearValues.size()); + renderPassInfo.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/31_compute_shader.cpp b/code/31_compute_shader.cpp new file mode 100644 index 00000000..e6901670 --- /dev/null +++ b/code/31_compute_shader.cpp @@ -0,0 +1,1414 @@ +// Sample by Sascha Willems +// Contact: webmaster@saschawillems.de + +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const uint32_t PARTICLE_COUNT = 8192; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsAndComputeFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsAndComputeFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct UniformBufferObject { + float deltaTime = 1.0f; +}; + +struct Particle { + glm::vec2 position; + glm::vec2 velocity; + glm::vec4 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Particle); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Particle, position); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Particle, color); + + return attributeDescriptions; + } +}; + +class ComputeShaderApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue computeQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkDescriptorSetLayout computeDescriptorSetLayout; + VkPipelineLayout computePipelineLayout; + VkPipeline computePipeline; + + VkCommandPool commandPool; + + std::vector shaderStorageBuffers; + std::vector shaderStorageBuffersMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + VkDescriptorPool descriptorPool; + std::vector computeDescriptorSets; + + std::vector commandBuffers; + std::vector computeCommandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector computeFinishedSemaphores; + std::vector inFlightFences; + std::vector computeInFlightFences; + uint32_t currentFrame = 0; + + float lastFrameTime = 0.0f; + + bool framebufferResized = false; + + double lastTime = 0.0f; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + + lastTime = glfwGetTime(); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createComputeDescriptorSetLayout(); + createGraphicsPipeline(); + createComputePipeline(); + createFramebuffers(); + createCommandPool(); + createShaderStorageBuffers(); + createUniformBuffers(); + createDescriptorPool(); + createComputeDescriptorSets(); + createCommandBuffers(); + createComputeCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + // We want to animate the particle system using the last frames time to get smooth, frame-rate independent animation + double currentTime = glfwGetTime(); + lastFrameTime = (currentTime - lastTime) * 1000.0; + lastTime = currentTime; + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + + vkDestroyPipeline(device, computePipeline, nullptr); + vkDestroyPipelineLayout(device, computePipelineLayout, nullptr); + + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, computeDescriptorSetLayout, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, shaderStorageBuffers[i], nullptr); + vkFreeMemory(device, shaderStorageBuffersMemory[i], nullptr); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroySemaphore(device, computeFinishedSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + vkDestroyFence(device, computeInFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsAndComputeFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsAndComputeFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.graphicsAndComputeFamily.value(), 0, &computeQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsAndComputeFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsAndComputeFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createComputeDescriptorSetLayout() { + std::array layoutBindings{}; + layoutBindings[0].binding = 0; + layoutBindings[0].descriptorCount = 1; + layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + layoutBindings[0].pImmutableSamplers = nullptr; + layoutBindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + + layoutBindings[1].binding = 1; + layoutBindings[1].descriptorCount = 1; + layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + layoutBindings[1].pImmutableSamplers = nullptr; + layoutBindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + + layoutBindings[2].binding = 2; + layoutBindings[2].descriptorCount = 1; + layoutBindings[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + layoutBindings[2].pImmutableSamplers = nullptr; + layoutBindings[2].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = 3; + layoutInfo.pBindings = layoutBindings.data(); + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create compute descriptor set layout!"); + } + } + + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Particle::getBindingDescription(); + auto attributeDescriptions = Particle::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pSetLayouts = nullptr; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createComputePipeline() { + auto computeShaderCode = readFile("shaders/comp.spv"); + + VkShaderModule computeShaderModule = createShaderModule(computeShaderCode); + + VkPipelineShaderStageCreateInfo computeShaderStageInfo{}; + computeShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + computeShaderStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; + computeShaderStageInfo.module = computeShaderModule; + computeShaderStageInfo.pName = "main"; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &computeDescriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &computePipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create compute pipeline layout!"); + } + + VkComputePipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + pipelineInfo.layout = computePipelineLayout; + pipelineInfo.stage = computeShaderStageInfo; + + if (vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &computePipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create compute pipeline!"); + } + + vkDestroyShaderModule(device, computeShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsAndComputeFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createShaderStorageBuffers() { + + // Initialize particles + std::default_random_engine rndEngine((unsigned)time(nullptr)); + std::uniform_real_distribution rndDist(0.0f, 1.0f); + + // Initial particle positions on a circle + std::vector particles(PARTICLE_COUNT); + for (auto& particle : particles) { + float r = 0.25f * sqrt(rndDist(rndEngine)); + float theta = rndDist(rndEngine) * 2.0f * 3.14159265358979323846f; + float x = r * cos(theta) * HEIGHT / WIDTH; + float y = r * sin(theta); + particle.position = glm::vec2(x, y); + particle.velocity = glm::normalize(glm::vec2(x,y)) * 0.00025f; + particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); + } + + VkDeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; + + // Create a staging buffer used to upload data to the gpu + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, particles.data(), (size_t)bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + shaderStorageBuffers.resize(MAX_FRAMES_IN_FLIGHT); + shaderStorageBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + // Copy initial particle data to all storage buffers + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, shaderStorageBuffers[i], shaderStorageBuffersMemory[i]); + copyBuffer(stagingBuffer, shaderStorageBuffers[i], bufferSize); + } + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } + } + + void createDescriptorPool() { + std::array poolSizes{}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + poolSizes[1].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT) * 2; + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = 2; + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createComputeDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, computeDescriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + computeDescriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo uniformBufferInfo{}; + uniformBufferInfo.buffer = uniformBuffers[i]; + uniformBufferInfo.offset = 0; + uniformBufferInfo.range = sizeof(UniformBufferObject); + + std::array descriptorWrites{}; + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = computeDescriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &uniformBufferInfo; + + VkDescriptorBufferInfo storageBufferInfoLastFrame{}; + storageBufferInfoLastFrame.buffer = shaderStorageBuffers[(i - 1) % MAX_FRAMES_IN_FLIGHT]; + storageBufferInfoLastFrame.offset = 0; + storageBufferInfoLastFrame.range = sizeof(Particle) * PARTICLE_COUNT; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = computeDescriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pBufferInfo = &storageBufferInfoLastFrame; + + VkDescriptorBufferInfo storageBufferInfoCurrentFrame{}; + storageBufferInfoCurrentFrame.buffer = shaderStorageBuffers[i]; + storageBufferInfoCurrentFrame.offset = 0; + storageBufferInfoCurrentFrame.range = sizeof(Particle) * PARTICLE_COUNT; + + descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[2].dstSet = computeDescriptorSets[i]; + descriptorWrites[2].dstBinding = 2; + descriptorWrites[2].dstArrayElement = 0; + descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[2].descriptorCount = 1; + descriptorWrites[2].pBufferInfo = &storageBufferInfoCurrentFrame; + + vkUpdateDescriptorSets(device, 3, descriptorWrites.data(), 0, nullptr); + } + } + + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void createComputeCommandBuffers() { + computeCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)computeCommandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, computeCommandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate compute command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &shaderStorageBuffers[currentFrame], offsets); + + vkCmdDraw(commandBuffer, PARTICLE_COUNT, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + void recordComputeCommandBuffer(VkCommandBuffer commandBuffer) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording compute command buffer!"); + } + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &computeDescriptorSets[currentFrame], 0, nullptr); + + vkCmdDispatch(commandBuffer, PARTICLE_COUNT / 256, 1, 1); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record compute command buffer!"); + } + + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + computeFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + computeInFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics synchronization objects for a frame!"); + } + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &computeFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &computeInFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create compute synchronization objects for a frame!"); + } + } + } + + void updateUniformBuffer(uint32_t currentImage) { + UniformBufferObject ubo{}; + ubo.deltaTime = lastFrameTime * 2.0f; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() { + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + // Compute submission + vkWaitForFences(device, 1, &computeInFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &computeInFlightFences[currentFrame]); + + vkResetCommandBuffer(computeCommandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordComputeCommandBuffer(computeCommandBuffers[currentFrame]); + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &computeCommandBuffers[currentFrame]; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &computeFinishedSemaphores[currentFrame]; + + if (vkQueueSubmit(computeQueue, 1, &submitInfo, computeInFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit compute command buffer!"); + }; + + // Graphics submission + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphore waitSemaphores[] = { computeFinishedSemaphores[currentFrame], imageAvailableSemaphores[currentFrame] }; + VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + submitInfo.waitSemaphoreCount = 2; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &renderFinishedSemaphores[currentFrame]; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if ((queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) && (queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT)) { + indices.graphicsAndComputeFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + ComputeShaderApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/31_shader_compute.comp b/code/31_shader_compute.comp new file mode 100644 index 00000000..5e188daa --- /dev/null +++ b/code/31_shader_compute.comp @@ -0,0 +1,40 @@ +#version 450 + +struct Particle { + vec2 position; + vec2 velocity; + vec4 color; +}; + +layout (binding = 0) uniform ParameterUBO { + float deltaTime; +} ubo; + +layout(std140, binding = 1) readonly buffer ParticleSSBOIn { + Particle particlesIn[ ]; +}; + +layout(std140, binding = 2) buffer ParticleSSBOOut { + Particle particlesOut[ ]; +}; + +layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in; + +void main() +{ + uint index = gl_GlobalInvocationID.x; + + Particle particleIn = particlesIn[index]; + + particlesOut[index].position = particleIn.position + particleIn.velocity.xy * ubo.deltaTime; + particlesOut[index].velocity = particleIn.velocity; + + // Flip movement at window border + if ((particlesOut[index].position.x <= -1.0) || (particlesOut[index].position.x >= 1.0)) { + particlesOut[index].velocity.x = -particlesOut[index].velocity.x; + } + if ((particlesOut[index].position.y <= -1.0) || (particlesOut[index].position.y >= 1.0)) { + particlesOut[index].velocity.y = -particlesOut[index].velocity.y; + } + +} \ No newline at end of file diff --git a/code/31_shader_compute.frag b/code/31_shader_compute.frag new file mode 100644 index 00000000..94517ecd --- /dev/null +++ b/code/31_shader_compute.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + + vec2 coord = gl_PointCoord - vec2(0.5); + outColor = vec4(fragColor, 0.5 - length(coord)); +} diff --git a/code/31_shader_compute.vert b/code/31_shader_compute.vert new file mode 100644 index 00000000..9730d27d --- /dev/null +++ b/code/31_shader_compute.vert @@ -0,0 +1,13 @@ +#version 450 + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec4 inColor; + +layout(location = 0) out vec3 fragColor; + +void main() { + + gl_PointSize = 14.0; + gl_Position = vec4(inPosition.xy, 1.0, 1.0); + fragColor = inColor.rgb; +} diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt new file mode 100644 index 00000000..4c409f92 --- /dev/null +++ b/code/CMakeLists.txt @@ -0,0 +1,186 @@ +cmake_minimum_required (VERSION 3.8) + +project (VulkanTutorial) + +find_package (glfw3 REQUIRED) +find_package (glm REQUIRED) +find_package (Vulkan REQUIRED) +find_package (tinyobjloader REQUIRED) + +find_package (PkgConfig) +pkg_get_variable (STB_INCLUDEDIR stb includedir) +if (NOT STB_INCLUDEDIR) + unset (STB_INCLUDEDIR) + find_path (STB_INCLUDEDIR stb_image.h PATH_SUFFIXES stb) +endif () +if (NOT STB_INCLUDEDIR) + message (FATAL_ERROR "stb_image.h not found") +endif () + +add_executable (glslang::validator IMPORTED) +find_program (GLSLANG_VALIDATOR "glslangValidator" HINTS $ENV{VULKAN_SDK}/bin REQUIRED) +set_property (TARGET glslang::validator PROPERTY IMPORTED_LOCATION "${GLSLANG_VALIDATOR}") + +function (add_shaders_target TARGET) + cmake_parse_arguments ("SHADER" "" "CHAPTER_NAME" "SOURCES" ${ARGN}) + set (SHADERS_DIR ${SHADER_CHAPTER_NAME}/shaders) + add_custom_command ( + OUTPUT ${SHADERS_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADERS_DIR} + ) + set (SHADERS ${SHADERS_DIR}/frag.spv ${SHADERS_DIR}/vert.spv) + # Some chapters may have compute shaders in addition to vertex and fragment shaders, + # so we conditionally check this and add them to the target + string(FIND "${SHADER_SOURCES}" "${CHAPTER_SHADER}.comp" COMPUTE_SHADER_INDEX) + if (${COMPUTE_SHADER_INDEX} GREATER -1) + set (SHADERS ${SHADERS} ${SHADERS_DIR}/comp.spv) + endif() + add_custom_command ( + OUTPUT ${SHADERS} + COMMAND glslang::validator + ARGS --target-env vulkan1.0 ${SHADER_SOURCES} --quiet + WORKING_DIRECTORY ${SHADERS_DIR} + DEPENDS ${SHADERS_DIR} ${SHADER_SOURCES} + COMMENT "Compiling Shaders" + VERBATIM + ) + add_custom_target (${TARGET} DEPENDS ${SHADERS}) +endfunction () + +function (add_chapter CHAPTER_NAME) + cmake_parse_arguments (CHAPTER "" "SHADER" "LIBS;TEXTURES;MODELS" ${ARGN}) + + add_executable (${CHAPTER_NAME} ${CHAPTER_NAME}.cpp) + set_target_properties (${CHAPTER_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}) + set_target_properties (${CHAPTER_NAME} PROPERTIES CXX_STANDARD 17) + target_link_libraries (${CHAPTER_NAME} Vulkan::Vulkan glfw) + target_include_directories (${CHAPTER_NAME} PRIVATE ${STB_INCLUDEDIR}) + + if (DEFINED CHAPTER_SHADER) + set (CHAPTER_SHADER_TARGET ${CHAPTER_NAME}_shader) + file (GLOB SHADER_SOURCES ${CHAPTER_SHADER}.frag ${CHAPTER_SHADER}.vert ${CHAPTER_SHADER}.comp) + add_shaders_target (${CHAPTER_SHADER_TARGET} CHAPTER_NAME ${CHAPTER_NAME} SOURCES ${SHADER_SOURCES}) + add_dependencies (${CHAPTER_NAME} ${CHAPTER_SHADER_TARGET}) + endif () + if (DEFINED CHAPTER_LIBS) + target_link_libraries (${CHAPTER_NAME} ${CHAPTER_LIBS}) + endif () + if (DEFINED CHAPTER_MODELS) + file (COPY ${CHAPTER_MODELS} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/models) + endif () + if (DEFINED CHAPTER_TEXTURES) + file (COPY ${CHAPTER_TEXTURES} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/textures) + endif () +endfunction () + +add_chapter (00_base_code) + +add_chapter (01_instance_creation) + +add_chapter (02_validation_layers) + +add_chapter (03_physical_device_selection) + +add_chapter (04_logical_device) + +add_chapter (05_window_surface) + +add_chapter (06_swap_chain_creation) + +add_chapter (07_image_views) + +add_chapter (08_graphics_pipeline) + +add_chapter (09_shader_modules + SHADER 09_shader_base) + +add_chapter (10_fixed_functions + SHADER 09_shader_base) + +add_chapter (11_render_passes + SHADER 09_shader_base) + +add_chapter (12_graphics_pipeline_complete + SHADER 09_shader_base) + +add_chapter (13_framebuffers + SHADER 09_shader_base) + +add_chapter (14_command_buffers + SHADER 09_shader_base) + +add_chapter (15_hello_triangle + SHADER 09_shader_base) + +add_chapter (16_frames_in_flight + SHADER 09_shader_base) + +add_chapter (17_swap_chain_recreation + SHADER 09_shader_base) + +add_chapter (18_vertex_input + SHADER 18_shader_vertexbuffer + LIBS glm::glm) + +add_chapter (19_vertex_buffer + SHADER 18_shader_vertexbuffer + LIBS glm::glm) + +add_chapter (20_staging_buffer + SHADER 18_shader_vertexbuffer + LIBS glm::glm) + +add_chapter (21_index_buffer + SHADER 18_shader_vertexbuffer + LIBS glm::glm) + +add_chapter (22_descriptor_set_layout + SHADER 22_shader_ubo + LIBS glm::glm) + +add_chapter (23_descriptor_sets + SHADER 22_shader_ubo + LIBS glm::glm) + +add_chapter (24_texture_image + SHADER 22_shader_ubo + TEXTURES ../images/texture.jpg + LIBS glm::glm) + +add_chapter (25_sampler + SHADER 22_shader_ubo + TEXTURES ../images/texture.jpg + LIBS glm::glm) + +add_chapter (26_texture_mapping + SHADER 26_shader_textures + TEXTURES ../images/texture.jpg + LIBS glm::glm) + +add_chapter (27_depth_buffering + SHADER 27_shader_depth + TEXTURES ../images/texture.jpg + LIBS glm::glm) + +add_chapter (28_model_loading + SHADER 27_shader_depth + MODELS ../resources/viking_room.obj + TEXTURES ../resources/viking_room.png + LIBS glm::glm tinyobjloader::tinyobjloader) + +add_chapter (29_mipmapping + SHADER 27_shader_depth + MODELS ../resources/viking_room.obj + TEXTURES ../resources/viking_room.png + LIBS glm::glm tinyobjloader::tinyobjloader) + +add_chapter (30_multisampling + SHADER 27_shader_depth + MODELS ../resources/viking_room.obj + TEXTURES ../resources/viking_room.png + LIBS glm::glm tinyobjloader::tinyobjloader) + +add_chapter (31_compute_shader + SHADER 31_shader_compute + LIBS glm::glm) diff --git a/code/incremental_patch.sh b/code/incremental_patch.sh new file mode 100755 index 00000000..6255af87 --- /dev/null +++ b/code/incremental_patch.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# Check if both a starting file and patch are provided +if [ $# != 2 ]; then + echo "usage: " + echo "specified patch will be applied to first_file.cpp and every code file larger than it (from later chapters)" + exit 1 +fi + +# Iterate over code files in order of increasing size +# i.e. in order of chapters (every chapter adds code) +apply_patch=false + +for f in `ls -Sr *.cpp` +do + # Apply patch on every code file including and after initial one + if [ $f = $1 ] || [ $apply_patch = true ]; then + apply_patch=true + + patch -f $f < $2 | grep -q "FAILED" > /dev/null + if [ $? = 0 ]; then + echo "failed to apply patch to $f" + exit 1 + fi + + rm -f *.orig + fi +done + +echo "patch successfully applied to all files" +exit 0 diff --git a/config.json b/config.json new file mode 100644 index 00000000..edb3a0b3 --- /dev/null +++ b/config.json @@ -0,0 +1,40 @@ +{ + "title": "Vulkan Tutorial", + "tagline": "A tutorial that teaches you everything it takes to render 3D graphics with the Vulkan API. It covers everything from Windows/Linux setup to rendering and debugging.", + "author": "Alexander Overvoorde", + "live": { + "inherit_index": true, + "clean_urls": true + }, + "html": { + "theme": "vulkan-vulkan", + "auto_landing": false, + "breadcrumbs": true, + "breadcrumb_separator": "Chevrons", + "date_modified": false, + "toggle_code": false, + "float": false, + "auto_toc": true, + + "links": { + "GitHub Repository": "https://github.com/Overv/VulkanTutorial", + "Support the website": "https://www.paypal.me/AOvervoorde", + "Vulkan Specification": "https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/", + "LunarG Vulkan SDK": "https://lunarg.com/vulkan-sdk/", + "Vulkan Guide": "https://docs.vulkan.org/guide/latest/", + "Vulkan Hardware Database": "https://vulkan.gpuinfo.org/", + "Rust code": "https://github.com/bwasty/vulkan-tutorial-rs", + "Java code": "https://github.com/Naitsirc98/Vulkan-Tutorial-Java", + "Go code": "https://github.com/vkngwrapper/vulkan-tutorial", + "Visual Studio 2019 samples": "https://github.com/jjYBdx4IL/VulkanTutorial-VisualStudioProjectFiles", + "Chinese translation": "https://github.com/fangcun010/VulkanTutorialCN" + } + }, + "ignore": { + "files": ["README.md", "build_ebook.py","daux.patch",".gitignore"], + "folders": ["ebook"] + }, + "languages": {"en": "English", "fr": "Français"}, + "language": "en", + "processor": "VulkanLinkProcessor" +} diff --git a/ebook/cover.kra b/ebook/cover.kra new file mode 100644 index 00000000..4e684aeb Binary files /dev/null and b/ebook/cover.kra differ diff --git a/ebook/cover.png b/ebook/cover.png new file mode 100644 index 00000000..17b4f57b Binary files /dev/null and b/ebook/cover.png differ diff --git a/ebook/listings-setup.tex b/ebook/listings-setup.tex new file mode 100644 index 00000000..0236f1bf --- /dev/null +++ b/ebook/listings-setup.tex @@ -0,0 +1,25 @@ +% Contents of listings-setup.tex +\usepackage{xcolor} + +\lstset{ + basicstyle=\ttfamily, + numbers=left, + keywordstyle=\color[rgb]{0.13,0.29,0.53}\bfseries, + stringstyle=\color[rgb]{0.31,0.60,0.02}, + commentstyle=\color[rgb]{0.56,0.35,0.01}\itshape, + numberstyle=\footnotesize, + stepnumber=1, + numbersep=5pt, + backgroundcolor=\color[RGB]{248,248,248}, + showspaces=false, + showstringspaces=false, + showtabs=false, + tabsize=2, + captionpos=b, + breaklines=true, + breakatwhitespace=true, + breakautoindent=true, + escapeinside={\%*}{*)}, + linewidth=\textwidth, + basewidth=0.5em, +} \ No newline at end of file diff --git a/en/00_Introduction.md b/en/00_Introduction.md new file mode 100644 index 00000000..1d14c781 --- /dev/null +++ b/en/00_Introduction.md @@ -0,0 +1,153 @@ +>## Read before following this tutorial +> +>This tutorial was written shortly after Vulkan was initially released, back in +>2016. A lot has changed since then and this tutorial no longer reflects the best +>way to use Vulkan today. +> +>Instead of reading this website, I recommend to follow the guide or one of the +>tutorials linked here: [https://vulkan.org/learn](https://vulkan.org/learn) + +## About + +This tutorial will teach you the basics of using the [Vulkan](https://www.khronos.org/vulkan/) +graphics and compute API. Vulkan is a new API by the [Khronos group](https://www.khronos.org/) +(known for OpenGL) that provides a much better abstraction of modern graphics +cards. This new interface allows you to better describe what your application +intends to do, which can lead to better performance and less surprising driver +behavior compared to existing APIs like [OpenGL](https://en.wikipedia.org/wiki/OpenGL) +and [Direct3D](https://en.wikipedia.org/wiki/Direct3D). The ideas behind Vulkan +are similar to those of [Direct3D 12](https://en.wikipedia.org/wiki/Direct3D#Direct3D_12) +and [Metal](https://en.wikipedia.org/wiki/Metal_(API)), but Vulkan has the +advantage of being fully cross-platform and allows you to develop for Windows, +Linux and Android at the same time. + +However, the price you pay for these benefits is that you have to work with a +significantly more verbose API. Every detail related to the graphics API needs +to be set up from scratch by your application, including initial frame buffer +creation and memory management for objects like buffers and texture images. The +graphics driver will do a lot less hand holding, which means that you will have +to do more work in your application to ensure correct behavior. + +The takeaway message here is that Vulkan is not for everyone. It is targeted at +programmers who are enthusiastic about high performance computer graphics, and +are willing to put some work in. If you are more interested in game development, +rather than computer graphics, then you may wish to stick to OpenGL or Direct3D, +which will not be deprecated in favor of Vulkan anytime soon. Another +alternative is to use an engine like [Unreal Engine](https://en.wikipedia.org/wiki/Unreal_Engine#Unreal_Engine_4) +or [Unity](https://en.wikipedia.org/wiki/Unity_(game_engine)), which will be +able to use Vulkan while exposing a much higher level API to you. + +With that out of the way, let's cover some prerequisites for following this +tutorial: + +* A graphics card and driver compatible with Vulkan ([NVIDIA](https://developer.nvidia.com/vulkan-driver), [AMD](http://www.amd.com/en-us/innovations/software-technologies/technologies-gaming/vulkan), [Intel](https://software.intel.com/en-us/blogs/2016/03/14/new-intel-vulkan-beta-1540204404-graphics-driver-for-windows-78110-1540), [Apple Silicon (Or the Apple M1)](https://www.phoronix.com/scan.php?page=news_item&px=Apple-Silicon-Vulkan-MoltenVK)) +* Experience with C++ (familiarity with RAII, initializer lists) +* A compiler with decent support of C++17 features (Visual Studio 2017+, GCC 7+, Or Clang 5+) +* Some existing experience with 3D computer graphics + +This tutorial will not assume knowledge of OpenGL or Direct3D concepts, but it +does require you to know the basics of 3D computer graphics. It will not explain +the math behind perspective projection, for example. See [this online book](https://paroj.github.io/gltut/) +for a great introduction of computer graphics concepts. Some other great computer graphics resources are: + +* [Ray tracing in one weekend](https://github.com/RayTracing/raytracing.github.io) +* [Physically Based Rendering book](http://www.pbr-book.org/) +* Vulkan being used in a real engine in the open-source [Quake](https://github.com/Novum/vkQuake) and [DOOM 3](https://github.com/DustinHLand/vkDOOM3) + +You can use C instead of C++ if you want, but you will have to use a different +linear algebra library and you will be on your own in terms of code structuring. +We will use C++ features like classes and RAII to organize logic and resource +lifetimes. There are also two alternative versions of this tutorial available for Rust developers: [Vulkano based](https://github.com/bwasty/vulkan-tutorial-rs), [Vulkanalia based](https://kylemayes.github.io/vulkanalia). + +To make it easier to follow along for developers using other programming languages, and to get some experience with the base API we'll be using the original C API to work with Vulkan. If you are using C++, however, you may prefer using the newer [Vulkan-Hpp](https://github.com/KhronosGroup/Vulkan-Hpp) bindings that abstract some of the dirty work and help prevent certain classes of errors. + +## E-book + +If you prefer to read this tutorial as an e-book, then you can download an EPUB +or PDF version here: + +* [EPUB](https://vulkan-tutorial.com/resources/vulkan_tutorial_en.epub) +* [PDF](https://vulkan-tutorial.com/resources/vulkan_tutorial_en.pdf) + +## Tutorial structure + +We'll start with an overview of how Vulkan works and the work we'll have to do +to get the first triangle on the screen. The purpose of all the smaller steps +will make more sense after you've understood their basic role in the whole +picture. Next, we'll set up the development environment with the [Vulkan SDK](https://lunarg.com/vulkan-sdk/), +the [GLM library](http://glm.g-truc.net/) for linear algebra operations and +[GLFW](http://www.glfw.org/) for window creation. The tutorial will cover how +to set these up on Windows with Visual Studio, and on Ubuntu Linux with GCC. + +After that we'll implement all of the basic components of a Vulkan program that +are necessary to render your first triangle. Each chapter will follow roughly +the following structure: + +* Introduce a new concept and its purpose +* Use all of the relevant API calls to integrate it into your program +* Abstract parts of it into helper functions + +Although each chapter is written as a follow-up on the previous one, it is also +possible to read the chapters as standalone articles introducing a certain +Vulkan feature. That means that the site is also useful as a reference. All of +the Vulkan functions and types are linked to the specification, so you can click +them to learn more. Vulkan is a very new API, so there may be some shortcomings +in the specification itself. You are encouraged to submit feedback to +[this Khronos repository](https://github.com/KhronosGroup/Vulkan-Docs). + +As mentioned before, the Vulkan API has a rather verbose API with many +parameters to give you maximum control over the graphics hardware. This causes +basic operations like creating a texture to take a lot of steps that have to be +repeated every time. Therefore we'll be creating our own collection of helper +functions throughout the tutorial. + +Every chapter will also conclude with a link to the full code listing up to that +point. You can refer to it if you have any doubts about the structure of the +code, or if you're dealing with a bug and want to compare. All of the code files +have been tested on graphics cards from multiple vendors to verify correctness. +Each chapter also has a comment section at the end where you can ask any +questions that are relevant to the specific subject matter. Please specify your +platform, driver version, source code, expected behavior and actual behavior to +help us help you. + +This tutorial is intended to be a community effort. Vulkan is still a very new +API and best practices have not really been established yet. If you have any +type of feedback on the tutorial and site itself, then please don't hesitate to +submit an issue or pull request to the [GitHub repository](https://github.com/Overv/VulkanTutorial). +You can *watch* the repository to be notified of updates to the tutorial. + +After you've gone through the ritual of drawing your very first Vulkan powered +triangle onscreen, we'll start expanding the program to include linear +transformations, textures and 3D models. + +If you've played with graphics APIs before, then you'll know that there can be a +lot of steps until the first geometry shows up on the screen. There are many of +these initial steps in Vulkan, but you'll see that each of the individual steps +is easy to understand and does not feel redundant. It's also important to keep +in mind that once you have that boring looking triangle, drawing fully textured +3D models does not take that much extra work, and each step beyond that point is +much more rewarding. + +If you encounter any problems while following the tutorial, then first check the +FAQ to see if your problem and its solution is already listed there. If you are +still stuck after that, then feel free to ask for help in the comment section of +the closest related chapter. + +Ready to dive into the future of high performance graphics APIs? [Let's go!](!en/Overview) + +## License + +Copyright (C) 2015-2023, Alexander Overvoorde + +The contents are licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), +unless stated otherwise. By contributing, you agree to license +your contributions to the public under that same license. + +The code listings in the `code` directory in the source repository are licensed +under [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/). +By contributing to that directory, you agree to license your contributions to +the public under that same public domain-like license. + +This program 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. diff --git a/en/01_Overview.md b/en/01_Overview.md new file mode 100644 index 00000000..306815b6 --- /dev/null +++ b/en/01_Overview.md @@ -0,0 +1,279 @@ +This chapter will start off with an introduction of Vulkan and the problems +it addresses. After that we're going to look at the ingredients that are +required for the first triangle. This will give you a big picture to place each +of the subsequent chapters in. We will conclude by covering the structure of the +Vulkan API and the general usage patterns. + +## Origin of Vulkan + +Just like the previous graphics APIs, Vulkan is designed as a cross-platform +abstraction over [GPUs](https://en.wikipedia.org/wiki/Graphics_processing_unit). +The problem with most of these APIs is that the era in which they were designed +featured graphics hardware that was mostly limited to configurable fixed +functionality. Programmers had to provide the vertex data in a standard format +and were at the mercy of the GPU manufacturers with regards to lighting and +shading options. + +As graphics card architectures matured, they started offering more and more +programmable functionality. All this new functionality had to be integrated with +the existing APIs somehow. This resulted in less than ideal abstractions and a +lot of guesswork on the graphics driver side to map the programmer's intent to +the modern graphics architectures. That's why there are so many driver updates +for improving the performance in games, sometimes by significant margins. +Because of the complexity of these drivers, application developers also need to +deal with inconsistencies between vendors, like the syntax that is accepted for +[shaders](https://en.wikipedia.org/wiki/Shader). Aside from these new features, +the past decade also saw an influx of mobile devices with powerful graphics +hardware. These mobile GPUs have different architectures based on their energy +and space requirements. One such example is [tiled rendering](https://en.wikipedia.org/wiki/Tiled_rendering), +which would benefit from improved performance by offering the programmer more +control over this functionality. Another limitation originating from the age of +these APIs is limited multi-threading support, which can result in a bottleneck +on the CPU side. + +Vulkan solves these problems by being designed from scratch for modern graphics +architectures. It reduces driver overhead by allowing programmers to clearly +specify their intent using a more verbose API, and allows multiple threads to +create and submit commands in parallel. It reduces inconsistencies in shader +compilation by switching to a standardized byte code format with a single +compiler. Lastly, it acknowledges the general purpose processing capabilities of +modern graphics cards by unifying the graphics and compute functionality into a +single API. + +## What it takes to draw a triangle + +We'll now look at an overview of all the steps it takes to render a triangle in +a well-behaved Vulkan program. All of the concepts introduced here will be +elaborated on in the next chapters. This is just to give you a big picture to +relate all of the individual components to. + +### Step 1 - Instance and physical device selection + +A Vulkan application starts by setting up the Vulkan API through a `VkInstance`. +An instance is created by describing your application and any API extensions you +will be using. After creating the instance, you can query for Vulkan supported +hardware and select one or more `VkPhysicalDevice`s to use for operations. You +can query for properties like VRAM size and device capabilities to select +desired devices, for example to prefer using dedicated graphics cards. + +### Step 2 - Logical device and queue families + +After selecting the right hardware device to use, you need to create a VkDevice +(logical device), where you describe more specifically which +VkPhysicalDeviceFeatures you will be using, like multi viewport rendering and +64 bit floats. You also need to specify which queue families you would like to +use. Most operations performed with Vulkan, like draw commands and memory +operations, are asynchronously executed by submitting them to a VkQueue. Queues +are allocated from queue families, where each queue family supports a specific +set of operations in its queues. For example, there could be separate queue +families for graphics, compute and memory transfer operations. The availability +of queue families could also be used as a distinguishing factor in physical +device selection. It is possible for a device with Vulkan support to not offer +any graphics functionality, however all graphics cards with Vulkan support today +will generally support all queue operations that we're interested in. + +### Step 3 - Window surface and swap chain + +Unless you're only interested in offscreen rendering, you will need to create a +window to present rendered images to. Windows can be created with the native +platform APIs or libraries like [GLFW](http://www.glfw.org/) and [SDL](https://www.libsdl.org/). +We will be using GLFW in this tutorial, but more about that in the next chapter. + +We need two more components to actually render to a window: a window surface +(VkSurfaceKHR) and a swap chain (VkSwapchainKHR). Note the `KHR` postfix, which +means that these objects are part of a Vulkan extension. The Vulkan API itself +is completely platform agnostic, which is why we need to use the standardized +WSI (Window System Interface) extension to interact with the window manager. The +surface is a cross-platform abstraction over windows to render to and is +generally instantiated by providing a reference to the native window handle, for +example `HWND` on Windows. Luckily, the GLFW library has a built-in function to +deal with the platform specific details of this. + +The swap chain is a collection of render targets. Its basic purpose is to ensure +that the image that we're currently rendering to is different from the one that +is currently on the screen. This is important to make sure that only complete +images are shown. Every time we want to draw a frame we have to ask the swap +chain to provide us with an image to render to. When we've finished drawing a +frame, the image is returned to the swap chain for it to be presented to the +screen at some point. The number of render targets and conditions for presenting +finished images to the screen depends on the present mode. Common present modes +are double buffering (vsync) and triple buffering. We'll look into these in the +swap chain creation chapter. + +Some platforms allow you to render directly to a display without interacting with any window manager through the `VK_KHR_display` and `VK_KHR_display_swapchain` extensions. These allow you to create a surface that represents the entire screen and could be used to implement your own window manager, for example. + +### Step 4 - Image views and framebuffers + +To draw to an image acquired from the swap chain, we have to wrap it into a +VkImageView and VkFramebuffer. An image view references a specific part of an +image to be used, and a framebuffer references image views that are to be used +for color, depth and stencil targets. Because there could be many different +images in the swap chain, we'll preemptively create an image view and +framebuffer for each of them and select the right one at draw time. + +### Step 5 - Render passes + +Render passes in Vulkan describe the type of images that are used during +rendering operations, how they will be used, and how their contents should be +treated. In our initial triangle rendering application, we'll tell Vulkan that +we will use a single image as color target and that we want it to be cleared +to a solid color right before the drawing operation. Whereas a render pass only +describes the type of images, a VkFramebuffer actually binds specific images to +these slots. + +### Step 6 - Graphics pipeline + +The graphics pipeline in Vulkan is set up by creating a VkPipeline object. It +describes the configurable state of the graphics card, like the viewport size +and depth buffer operation and the programmable state using VkShaderModule +objects. The VkShaderModule objects are created from shader byte code. The +driver also needs to know which render targets will be used in the pipeline, +which we specify by referencing the render pass. + +One of the most distinctive features of Vulkan compared to existing APIs, is +that almost all configuration of the graphics pipeline needs to be set in advance. +That means that if you want to switch to a different shader or slightly +change your vertex layout, then you need to entirely recreate the graphics +pipeline. That means that you will have to create many VkPipeline objects in +advance for all the different combinations you need for your rendering +operations. Only some basic configuration, like viewport size and clear color, +can be changed dynamically. All of the state also needs to be described +explicitly, there is no default color blend state, for example. + +The good news is that because you're doing the equivalent of ahead-of-time +compilation versus just-in-time compilation, there are more optimization +opportunities for the driver and runtime performance is more predictable, +because large state changes like switching to a different graphics pipeline are +made very explicit. + +### Step 7 - Command pools and command buffers + +As mentioned earlier, many of the operations in Vulkan that we want to execute, +like drawing operations, need to be submitted to a queue. These operations first +need to be recorded into a VkCommandBuffer before they can be submitted. These +command buffers are allocated from a `VkCommandPool` that is associated with a +specific queue family. To draw a simple triangle, we need to record a command +buffer with the following operations: + +* Begin the render pass +* Bind the graphics pipeline +* Draw 3 vertices +* End the render pass + +Because the image in the framebuffer depends on which specific image the swap +chain will give us, we need to record a command buffer for each possible image +and select the right one at draw time. The alternative would be to record the +command buffer again every frame, which is not as efficient. + +### Step 8 - Main loop + +Now that the drawing commands have been wrapped into a command buffer, the main +loop is quite straightforward. We first acquire an image from the swap chain +with vkAcquireNextImageKHR. We can then select the appropriate command buffer +for that image and execute it with vkQueueSubmit. Finally, we return the image +to the swap chain for presentation to the screen with vkQueuePresentKHR. + +Operations that are submitted to queues are executed asynchronously. Therefore +we have to use synchronization objects like semaphores to ensure a correct +order of execution. Execution of the draw command buffer must be set up to wait +on image acquisition to finish, otherwise it may occur that we start rendering +to an image that is still being read for presentation on the screen. The +vkQueuePresentKHR call in turn needs to wait for rendering to be finished, for +which we'll use a second semaphore that is signaled after rendering completes. + +### Summary + +This whirlwind tour should give you a basic understanding of the work ahead for +drawing the first triangle. A real-world program contains more steps, like +allocating vertex buffers, creating uniform buffers and uploading texture images +that will be covered in subsequent chapters, but we'll start simple because +Vulkan has enough of a steep learning curve as it is. Note that we'll cheat a +bit by initially embedding the vertex coordinates in the vertex shader instead +of using a vertex buffer. That's because managing vertex buffers requires some +familiarity with command buffers first. + +So in short, to draw the first triangle we need to: + +* Create a VkInstance +* Select a supported graphics card (VkPhysicalDevice) +* Create a VkDevice and VkQueue for drawing and presentation +* Create a window, window surface and swap chain +* Wrap the swap chain images into VkImageView +* Create a render pass that specifies the render targets and usage +* Create framebuffers for the render pass +* Set up the graphics pipeline +* Allocate and record a command buffer with the draw commands for every possible +swap chain image +* Draw frames by acquiring images, submitting the right draw command buffer and +returning the images back to the swap chain + +It's a lot of steps, but the purpose of each individual step will be made very +simple and clear in the upcoming chapters. If you're confused about the relation +of a single step compared to the whole program, you should refer back to this +chapter. + +## API concepts + +This chapter will conclude with a short overview of how the Vulkan API is +structured at a lower level. + +### Coding conventions + +All of the Vulkan functions, enumerations and structs are defined in the +`vulkan.h` header, which is included in the [Vulkan SDK](https://lunarg.com/vulkan-sdk/) +developed by LunarG. We'll look into installing this SDK in the next chapter. + +Functions have a lower case `vk` prefix, types like enumerations and structs +have a `Vk` prefix and enumeration values have a `VK_` prefix. The API heavily +uses structs to provide parameters to functions. For example, object creation +generally follows this pattern: + +```c++ +VkXXXCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO; +createInfo.pNext = nullptr; +createInfo.foo = ...; +createInfo.bar = ...; + +VkXXX object; +if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) { + std::cerr << "failed to create object" << std::endl; + return false; +} +``` + +Many structures in Vulkan require you to explicitly specify the type of +structure in the `sType` member. The `pNext` member can point to an extension +structure and will always be `nullptr` in this tutorial. Functions that create +or destroy an object will have a VkAllocationCallbacks parameter that allows you +to use a custom allocator for driver memory, which will also be left `nullptr` +in this tutorial. + +Almost all functions return a VkResult that is either `VK_SUCCESS` or an error +code. The specification describes which error codes each function can return and +what they mean. + +### Validation layers + +As mentioned earlier, Vulkan is designed for high performance and low driver +overhead. Therefore it will include very limited error checking and debugging +capabilities by default. The driver will often crash instead of returning an +error code if you do something wrong, or worse, it will appear to work on your +graphics card and completely fail on others. + +Vulkan allows you to enable extensive checks through a feature known as +*validation layers*. Validation layers are pieces of code that can be inserted +between the API and the graphics driver to do things like running extra checks +on function parameters and tracking memory management problems. The nice thing +is that you can enable them during development and then completely disable them +when releasing your application for zero overhead. Anyone can write their own +validation layers, but the Vulkan SDK by LunarG provides a standard set of +validation layers that we'll be using in this tutorial. You also need to +register a callback function to receive debug messages from the layers. + +Because Vulkan is so explicit about every operation and the validation layers +are so extensive, it can actually be a lot easier to find out why your screen is +black compared to OpenGL and Direct3D! + +There's only one more step before we'll start writing code and that's [setting +up the development environment](!en/Development_environment). diff --git a/en/02_Development_environment.md b/en/02_Development_environment.md new file mode 100644 index 00000000..2d8b2535 --- /dev/null +++ b/en/02_Development_environment.md @@ -0,0 +1,544 @@ +In this chapter we'll set up your environment for developing Vulkan applications +and install some useful libraries. All of the tools we'll use, with the +exception of the compiler, are compatible with Windows, Linux and MacOS, but the +steps for installing them differ a bit, which is why they're described +separately here. + +## Windows + +If you're developing for Windows, then I will assume that you are using Visual +Studio to compile your code. For complete C++17 support, you need to use either +Visual Studio 2017 or 2019. The steps outlined below were written for VS 2017. + +### Vulkan SDK + +The most important component you'll need for developing Vulkan applications is +the SDK. It includes the headers, standard validation layers, debugging tools +and a loader for the Vulkan functions. The loader looks up the functions in the +driver at runtime, similarly to GLEW for OpenGL - if you're familiar with that. + +The SDK can be downloaded from [the LunarG website](https://vulkan.lunarg.com/) +using the buttons at the bottom of the page. You don't have to create an +account, but it will give you access to some additional documentation that may +be useful to you. + +![](/images/vulkan_sdk_download_buttons.png) + +Proceed through the installation and pay attention to the install location of +the SDK. The first thing we'll do is verify that your graphics card and driver +properly support Vulkan. Go to the directory where you installed the SDK, open +the `Bin` directory and run the `vkcube.exe` demo. You should see the following: + +![](/images/cube_demo.png) + +If you receive an error message then ensure that your drivers are up-to-date, +include the Vulkan runtime and that your graphics card is supported. See the +[introduction chapter](!en/Introduction) for links to drivers from the major +vendors. + +There is another program in this directory that will be useful for development. The `glslangValidator.exe` and `glslc.exe` programs will be used to compile shaders from the +human-readable [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to +bytecode. We'll cover this in depth in the [shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) +chapter. The `Bin` directory also contains the binaries of the Vulkan loader +and the validation layers, while the `Lib` directory contains the libraries. + +Lastly, there's the `Include` directory that contains the Vulkan headers. Feel free to explore the other files, but we won't need them for this tutorial. + +### GLFW + +As mentioned before, Vulkan by itself is a platform agnostic API and does not +include tools for creating a window to display the rendered results. To benefit +from the cross-platform advantages of Vulkan and to avoid the horrors of Win32, +we'll use the [GLFW library](http://www.glfw.org/) to create a window, which +supports Windows, Linux and MacOS. There are other libraries available for this +purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that +it also abstracts away some of the other platform-specific things in Vulkan +besides just window creation. + +You can find the latest release of GLFW on the [official website](http://www.glfw.org/download.html). +In this tutorial we'll be using the 64-bit binaries, but you can of course also +choose to build in 32 bit mode. In that case make sure to link with the Vulkan +SDK binaries in the `Lib32` directory instead of `Lib`. After downloading it, extract the archive +to a convenient location. I've chosen to create a `Libraries` directory in the +Visual Studio directory under documents. + +![](/images/glfw_directory.png) + +### GLM + +Unlike DirectX 12, Vulkan does not include a library for linear algebra +operations, so we'll have to download one. [GLM](http://glm.g-truc.net/) is a +nice library that is designed for use with graphics APIs and is also commonly +used with OpenGL. + +GLM is a header-only library, so just download the [latest version](https://github.com/g-truc/glm/releases) +and store it in a convenient location. You should have a directory structure +similar to the following now: + +![](/images/library_directory.png) + +### Setting up Visual Studio + +Now that you've installed all of the dependencies we can set up a basic Visual +Studio project for Vulkan and write a little bit of code to make sure that +everything works. + +Start Visual Studio and create a new `Windows Desktop Wizard` project by entering a name and pressing `OK`. + +![](/images/vs_new_cpp_project.png) + +Make sure that `Console Application (.exe)` is selected as application type so that we have a place to print debug messages to, and check `Empty Project` to prevent Visual Studio from adding boilerplate code. + +![](/images/vs_application_settings.png) + +Press `OK` to create the project and add a C++ source file. You should +already know how to do that, but the steps are included here for completeness. + +![](/images/vs_new_item.png) + +![](/images/vs_new_source_file.png) + +Now add the following code to the file. Don't worry about trying to +understand it right now; we're just making sure that you can compile and run +Vulkan applications. We'll start from scratch in the next chapter. + +```c++ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include + +int main() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan window", nullptr, nullptr); + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + + std::cout << extensionCount << " extensions supported\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Let's now configure the project to get rid of the errors. Open the project +properties dialog and ensure that `All Configurations` is selected, because most +of the settings apply to both `Debug` and `Release` mode. + +![](/images/vs_open_project_properties.png) + +![](/images/vs_all_configs.png) + +Go to `C++ -> General -> Additional Include Directories` and press `` +in the dropdown box. + +![](/images/vs_cpp_general.png) + +Add the header directories for Vulkan, GLFW and GLM: + +![](/images/vs_include_dirs.png) + +Next, open the editor for library directories under `Linker -> General`: + +![](/images/vs_link_settings.png) + +And add the locations of the object files for Vulkan and GLFW: + +![](/images/vs_link_dirs.png) + +Go to `Linker -> Input` and press `` in the `Additional Dependencies` +dropdown box. + +![](/images/vs_link_input.png) + +Enter the names of the Vulkan and GLFW object files: + +![](/images/vs_dependencies.png) + +And finally change the compiler to support C++17 features: + +![](/images/vs_cpp17.png) + +You can now close the project properties dialog. If you did everything right +then you should no longer see any more errors being highlighted in the code. + +Finally, ensure that you are actually compiling in 64 bit mode: + +![](/images/vs_build_mode.png) + +Press `F5` to compile and run the project and you should see a command prompt +and a window pop up like this: + +![](/images/vs_test_window.png) + +The number of extensions should be non-zero. Congratulations, you're all set for +[playing with Vulkan](!en/Drawing_a_triangle/Setup/Base_code)! + +## Linux + +These instructions will be aimed at Ubuntu, Fedora and Arch Linux users, but you may be able to follow +along by changing the package manager-specific commands to the ones that are appropriate for you. You should have a compiler that supports C++17 (GCC 7+ or Clang 5+). You'll also need `make`. + +### Vulkan Packages + +The most important components you'll need for developing Vulkan applications on Linux are the Vulkan loader, validation layers, and a couple of command-line utilities to test whether your machine is Vulkan-capable: + +* `sudo apt install vulkan-tools` or `sudo dnf install vulkan-tools`: Command-line utilities, most importantly `vulkaninfo` and `vkcube`. Run these to confirm your machine supports Vulkan. +* `sudo apt install libvulkan-dev` or `sudo dnf install vulkan-loader-devel` : Installs Vulkan loader. The loader looks up the functions in the driver at runtime, similarly to GLEW for OpenGL - if you're familiar with that. +* `sudo apt install vulkan-validationlayers spirv-tools` or `sudo dnf install mesa-vulkan-drivers vulkan-validation-layers-devel`: Installs the standard validation layers and required SPIR-V tools. These are crucial when debugging Vulkan applications, and we'll discuss them in the upcoming chapter. + +On Arch Linux, you can run `sudo pacman -S vulkan-devel` to install all the +required tools above. + +If installation was successful, you should be all set with the Vulkan portion. Remember to run + `vkcube` and ensure you see the following pop up in a window: + +![](/images/cube_demo_nowindow.png) + +If you receive an error message then ensure that your drivers are up-to-date, +include the Vulkan runtime and that your graphics card is supported. See the +[introduction chapter](!en/Introduction) for links to drivers from the major +vendors. + +### X Window System and XFree86-VidModeExtension +It is possible that these libraries are not on the system, if not, you can install them using the following commands: +* `sudo apt install libxxf86vm-dev` or `dnf install libXxf86vm-devel`: Provides an interface to the XFree86-VidModeExtension. +* `sudo apt install libxi-dev` or `dnf install libXi-devel`: Provides an X Window System client interface to the XINPUT extension. + +### GLFW + +As mentioned before, Vulkan by itself is a platform agnostic API and does not +include tools for creation a window to display the rendered results. To benefit +from the cross-platform advantages of Vulkan and to avoid the horrors of X11, +we'll use the [GLFW library](http://www.glfw.org/) to create a window, which +supports Windows, Linux and MacOS. There are other libraries available for this +purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that +it also abstracts away some of the other platform-specific things in Vulkan +besides just window creation. + +We'll be installing GLFW from the following command: + +```bash +sudo apt install libglfw3-dev +``` +or +```bash +sudo dnf install glfw-devel +``` +or +```bash +sudo pacman -S glfw +``` + +### GLM + +Unlike DirectX 12, Vulkan does not include a library for linear algebra +operations, so we'll have to download one. [GLM](http://glm.g-truc.net/) is a +nice library that is designed for use with graphics APIs and is also commonly +used with OpenGL. + +It is a header-only library that can be installed from the `libglm-dev` or +`glm-devel` package: + +```bash +sudo apt install libglm-dev +``` +or +```bash +sudo dnf install glm-devel +``` +or +```bash +sudo pacman -S glm +``` + +### Shader Compiler + +We have just about all we need, except we'll want a program to compile shaders from the human-readable [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to bytecode. + +Two popular shader compilers are Khronos Group's `glslangValidator` and Google's `glslc`. The latter has a familiar GCC- and Clang-like usage, so we'll go with that: on Ubuntu, download Google's [unofficial binaries](https://github.com/google/shaderc/blob/main/downloads.md) and copy `glslc` to your `/usr/local/bin`. Note you may need to `sudo` depending on your permissions. On Fedora use `sudo dnf install glslc`, while on Arch Linux run `sudo pacman -S shaderc`. To test, run `glslc` and it should rightfully complain we didn't pass any shaders to compile: + +`glslc: error: no input files` + +We'll cover `glslc` in depth in the [shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) chapter. + +### Setting up a makefile project + +Now that you have installed all of the dependencies, we can set up a basic +makefile project for Vulkan and write a little bit of code to make sure that +everything works. + +Create a new directory at a convenient location with a name like `VulkanTest`. +Create a source file called `main.cpp` and insert the following code. Don't +worry about trying to understand it right now; we're just making sure that you +can compile and run Vulkan applications. We'll start from scratch in the next +chapter. + +```c++ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include + +int main() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan window", nullptr, nullptr); + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + + std::cout << extensionCount << " extensions supported\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Next, we'll write a makefile to compile and run this basic Vulkan code. Create a +new empty file called `Makefile`. I will assume that you already have some basic +experience with makefiles, like how variables and rules work. If not, you can +get up to speed very quickly with [this tutorial](https://makefiletutorial.com/). + +We'll first define a couple of variables to simplify the remainder of the file. +Define a `CFLAGS` variable that will specify the basic compiler flags: + +```make +CFLAGS = -std=c++17 -O2 +``` + +We're going to use modern C++ (`-std=c++17`), and we'll set optimization level to O2. We can remove -O2 to compile programs faster, but we should remember to place it back for release builds. + +Similarly, define the linker flags in a `LDFLAGS` variable: + +```make +LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi +``` + +The flag `-lglfw` is for GLFW, `-lvulkan` links with the Vulkan function loader and the remaining flags are low-level system libraries that GLFW needs. The remaining flags are dependencies of GLFW itself: the threading and window management. + +It is possible that the `Xxf86vm` and `Xi` libraries are not yet installed on your system. You can find them in the following packages: + +```bash +sudo apt install libxxf86vm-dev libxi-dev +``` +or +```bash +sudo dnf install libXi-devel libXxf86vm-devel +``` +or +```bash +sudo pacman -S libxi libxxf86vm +``` + +Specifying the rule to compile `VulkanTest` is straightforward now. Make sure to +use tabs for indentation instead of spaces. + +```make +VulkanTest: main.cpp + g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS) +``` + +Verify that this rule works by saving the makefile and running `make` in the +directory with `main.cpp` and `Makefile`. This should result in a `VulkanTest` +executable. + +We'll now define two more rules, `test` and `clean`, where the former will +run the executable and the latter will remove a built executable: + +```make +.PHONY: test clean + +test: VulkanTest + ./VulkanTest + +clean: + rm -f VulkanTest +``` + +Running `make test` should show the program running successfully, and displaying the number of Vulkan extensions. The application should exit with the success return code (`0`) when you close the empty window. You should now have a complete makefile that resembles the following: + +```make +CFLAGS = -std=c++17 -O2 +LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi + +VulkanTest: main.cpp + g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS) + +.PHONY: test clean + +test: VulkanTest + ./VulkanTest + +clean: + rm -f VulkanTest +``` + +You can now use this directory as a template for your Vulkan projects. Make a copy, rename it to something like `HelloTriangle` and remove all of the code in `main.cpp`. + +You are now all set for [the real adventure](!en/Drawing_a_triangle/Setup/Base_code). + +## MacOS + +These instructions will assume you are using Xcode and the [Homebrew package manager](https://brew.sh/). Also, keep in mind that you will need at least MacOS version 10.11, and your device needs to support the [Metal API](https://en.wikipedia.org/wiki/Metal_(API)#Supported_GPUs). + +### Vulkan SDK + +The most important component you'll need for developing Vulkan applications is the SDK. It includes the headers, standard validation layers, debugging tools and a loader for the Vulkan functions. The loader looks up the functions in the driver at runtime, similarly to GLEW for OpenGL - if you're familiar with that. + +The SDK can be downloaded from [the LunarG website](https://vulkan.lunarg.com/) using the buttons at the bottom of the page. You don't have to create an account, but it will give you access to some additional documentation that may be useful to you. + +![](/images/vulkan_sdk_download_buttons.png) + +The SDK version for MacOS internally uses [MoltenVK](https://moltengl.com/). There is no native support for Vulkan on MacOS, so what MoltenVK does is actually act as a layer that translates Vulkan API calls to Apple's Metal graphics framework. With this you can take advantage of debugging and performance benefits of Apple's Metal framework. + +After downloading it, simply extract the contents to a folder of your choice (keep in mind you will need to reference it when creating your projects on Xcode). Inside the extracted folder, in the `Applications` folder you should have some executable files that will run a few demos using the SDK. Run the `vkcube` executable and you will see the following: + +![](/images/cube_demo_mac.png) + +### GLFW + +As mentioned before, Vulkan by itself is a platform agnostic API and does not include tools for creation a window to display the rendered results. We'll use the [GLFW library](http://www.glfw.org/) to create a window, which supports Windows, Linux and MacOS. There are other libraries available for this purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that it also abstracts away some of the other platform-specific things in Vulkan besides just window creation. + +To install GLFW on MacOS we will use the Homebrew package manager to get the `glfw` package: + +```bash +brew install glfw +``` + +### GLM + +Vulkan does not include a library for linear algebra operations, so we'll have to download one. [GLM](http://glm.g-truc.net/) is a nice library that is designed for use with graphics APIs and is also commonly used with OpenGL. + +It is a header-only library that can be installed from the `glm` package: + +```bash +brew install glm +``` + +### Setting up Xcode + +Now that all the dependencies are installed we can set up a basic Xcode project for Vulkan. Most of the instructions here are essentially a lot of "plumbing" so we can get all the dependencies linked to the project. Also, keep in mind that during the following instructions whenever we mention the folder `vulkansdk` we are refering to the folder where you extracted the Vulkan SDK. + +Start Xcode and create a new Xcode project. On the window that will open select Application > Command Line Tool. + +![](/images/xcode_new_project.png) + +Select `Next`, write a name for the project and for `Language` select `C++`. + +![](/images/xcode_new_project_2.png) + +Press `Next` and the project should have been created. Now, let's change the code in the generated `main.cpp` file to the following code: + +```c++ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include + +int main() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan window", nullptr, nullptr); + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + + std::cout << extensionCount << " extensions supported\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Keep in mind you are not required to understand all this code is doing yet, we are just setting up some API calls to make sure everything is working. + +Xcode should already be showing some errors such as libraries it cannot find. We will now start configuring the project to get rid of those errors. On the *Project Navigator* panel select your project. Open the *Build Settings* tab and then: + +* Find the **Header Search Paths** field and add a link to `/usr/local/include` (this is where Homebrew installs headers, so the glm and glfw3 header files should be there) and a link to `vulkansdk/macOS/include` for the Vulkan headers. +* Find the **Library Search Paths** field and add a link to `/usr/local/lib` (again, this is where Homebrew installs libraries, so the glm and glfw3 lib files should be there) and a link to `vulkansdk/macOS/lib`. + +It should look like so (obviously, paths will be different depending on where you placed on your files): + +![](/images/xcode_paths.png) + +Now, in the *Build Phases* tab, on **Link Binary With Libraries** we will add both the `glfw3` and the `vulkan` frameworks. To make things easier we will be adding the dynamic libraries in the project (you can check the documentation of these libraries if you want to use the static frameworks). + +* For glfw open the folder `/usr/local/lib` and there you will find a file name like `libglfw.3.x.dylib` ("x" is the library's version number, it might be different depending on when you downloaded the package from Homebrew). Simply drag that file to the Linked Frameworks and Libraries tab on Xcode. +* For vulkan, go to `vulkansdk/macOS/lib`. Do the same for the both files `libvulkan.1.dylib` and `libvulkan.1.x.xx.dylib` (where "x" will be the version number of the the SDK you downloaded). + +After adding those libraries, in the same tab on **Copy Files** change `Destination` to "Frameworks", clear the subpath and deselect "Copy only when installing". Click on the "+" sign and add all those three frameworks here aswell. + +Your Xcode configuration should look like: + +![](/images/xcode_frameworks.png) + +The last thing you need to setup are a couple of environment variables. On Xcode toolbar go to `Product` > `Scheme` > `Edit Scheme...`, and in the `Arguments` tab add the two following environment variables: + +* VK_ICD_FILENAMES = `vulkansdk/macOS/share/vulkan/icd.d/MoltenVK_icd.json` +* VK_LAYER_PATH = `vulkansdk/macOS/share/vulkan/explicit_layer.d` + +It should look like so: + +![](/images/xcode_variables.png) + +Finally, you should be all set! Now if you run the project (remembering to setting the build configuration to Debug or Release depending on the configuration you chose) you should see the following: + +![](/images/xcode_output.png) + +The number of extensions should be non-zero. The other logs are from the libraries, you might get different messages from those depending on your configuration. + +You are now all set for [the real thing](!en/Drawing_a_triangle/Setup/Base_code). diff --git a/en/03_Drawing_a_triangle/00_Setup/00_Base_code.md b/en/03_Drawing_a_triangle/00_Setup/00_Base_code.md new file mode 100644 index 00000000..e5b0e0b3 --- /dev/null +++ b/en/03_Drawing_a_triangle/00_Setup/00_Base_code.md @@ -0,0 +1,223 @@ +## General structure + +In the previous chapter you've created a Vulkan project with all of the proper +configuration and tested it with the sample code. In this chapter we're starting +from scratch with the following code: + +```c++ +#include + +#include +#include +#include + +class HelloTriangleApplication { +public: + void run() { + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + void initVulkan() { + + } + + void mainLoop() { + + } + + void cleanup() { + + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} +``` + +We first include the Vulkan header from the LunarG SDK, which provides the +functions, structures and enumerations. The `stdexcept` and `iostream` headers +are included for reporting and propagating errors. The `cstdlib` +header provides the `EXIT_SUCCESS` and `EXIT_FAILURE` macros. + +The program itself is wrapped into a class where we'll store the Vulkan objects +as private class members and add functions to initiate each of them, which will +be called from the `initVulkan` function. Once everything has been prepared, we +enter the main loop to start rendering frames. We'll fill in the `mainLoop` +function to include a loop that iterates until the window is closed in a moment. +Once the window is closed and `mainLoop` returns, we'll make sure to deallocate +the resources we've used in the `cleanup` function. + +If any kind of fatal error occurs during execution then we'll throw a +`std::runtime_error` exception with a descriptive message, which will propagate +back to the `main` function and be printed to the command prompt. To handle +a variety of standard exception types as well, we catch the more general `std::exception`. One example of an error that we will deal with soon is finding +out that a certain required extension is not supported. + +Roughly every chapter that follows after this one will add one new function that +will be called from `initVulkan` and one or more new Vulkan objects to the +private class members that need to be freed at the end in `cleanup`. + +## Resource management + +Just like each chunk of memory allocated with `malloc` requires a call to +`free`, every Vulkan object that we create needs to be explicitly destroyed when +we no longer need it. In C++ it is possible to perform automatic resource +management using [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) +or smart pointers provided in the `` header. However, I've chosen to be +explicit about allocation and deallocation of Vulkan objects in this tutorial. +After all, Vulkan's niche is to be explicit about every operation to avoid +mistakes, so it's good to be explicit about the lifetime of objects to learn how +the API works. + +After following this tutorial, you could implement automatic resource management +by writing C++ classes that acquire Vulkan objects in their constructor and +release them in their destructor, or by providing a custom deleter to either +`std::unique_ptr` or `std::shared_ptr`, depending on your ownership requirements. +RAII is the recommended model for larger Vulkan programs, but +for learning purposes it's always good to know what's going on behind the +scenes. + +Vulkan objects are either created directly with functions like `vkCreateXXX`, or +allocated through another object with functions like `vkAllocateXXX`. After +making sure that an object is no longer used anywhere, you need to destroy it +with the counterparts `vkDestroyXXX` and `vkFreeXXX`. The parameters for these +functions generally vary for different types of objects, but there is one +parameter that they all share: `pAllocator`. This is an optional parameter that +allows you to specify callbacks for a custom memory allocator. We will ignore +this parameter in the tutorial and always pass `nullptr` as argument. + +## Integrating GLFW + +Vulkan works perfectly fine without creating a window if you want to use it for +off-screen rendering, but it's a lot more exciting to actually show something! +First replace the `#include ` line with + +```c++ +#define GLFW_INCLUDE_VULKAN +#include +``` + +That way GLFW will include its own definitions and automatically load the Vulkan +header with it. Add a `initWindow` function and add a call to it from the `run` +function before the other calls. We'll use that function to initialize GLFW and +create a window. + +```c++ +void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); +} + +private: + void initWindow() { + + } +``` + +The very first call in `initWindow` should be `glfwInit()`, which initializes +the GLFW library. Because GLFW was originally designed to create an OpenGL +context, we need to tell it to not create an OpenGL context with a subsequent +call: + +```c++ +glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); +``` + +Because handling resized windows takes special care that we'll look into later, +disable it for now with another window hint call: + +```c++ +glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); +``` + +All that's left now is creating the actual window. Add a private class member to store a reference to it: + +```c++ +private: +GLFWwindow* window; +``` + +Initialize the window with + +```c++ +window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); +``` + +The first three parameters specify the width, height and title of the window. +The fourth parameter allows you to optionally specify a monitor to open the +window on and the last parameter is only relevant to OpenGL. + +It's a good idea to use constants instead of hardcoded width and height numbers +because we'll be referring to these values a couple of times in the future. I've +added the following lines above the `HelloTriangleApplication` class definition: + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; +``` + +and replaced the window creation call with + +```c++ +window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +``` + +You should now have a `initWindow` function that looks like this: + +```c++ +void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +} +``` + +To keep the application running until either an error occurs or the window is +closed, we need to add an event loop to the `mainLoop` function as follows: + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } +} +``` + +This code should be fairly self-explanatory. It loops and checks for events like +pressing the X button until the window has been closed by the user. This is also +the loop where we'll later call a function to render a single frame. + +Once the window is closed, we need to clean up resources by destroying it and +terminating GLFW itself. This will be our first `cleanup` code: + +```c++ +void cleanup() { + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +When you run the program now you should see a window titled `Vulkan` show up +until the application is terminated by closing the window. Now that we have the +skeleton for the Vulkan application, let's [create the first Vulkan object](!en/Drawing_a_triangle/Setup/Instance)! + +[C++ code](/code/00_base_code.cpp) diff --git a/en/03_Drawing_a_triangle/00_Setup/01_Instance.md b/en/03_Drawing_a_triangle/00_Setup/01_Instance.md new file mode 100644 index 00000000..d9744a1c --- /dev/null +++ b/en/03_Drawing_a_triangle/00_Setup/01_Instance.md @@ -0,0 +1,221 @@ +## Creating an instance + +The very first thing you need to do is initialize the Vulkan library by creating +an *instance*. The instance is the connection between your application and the +Vulkan library and creating it involves specifying some details about your +application to the driver. + +Start by adding a `createInstance` function and invoking it in the +`initVulkan` function. + +```c++ +void initVulkan() { + createInstance(); +} +``` + +Additionally add a data member to hold the handle to the instance: + +```c++ +private: +VkInstance instance; +``` + +Now, to create an instance we'll first have to fill in a struct with some +information about our application. This data is technically optional, but it may +provide some useful information to the driver in order to optimize our specific +application (e.g. because it uses a well-known graphics engine with +certain special behavior). This struct is called `VkApplicationInfo`: + +```c++ +void createInstance() { + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; +} +``` + +As mentioned before, many structs in Vulkan require you to explicitly specify +the type in the `sType` member. This is also one of the many structs with a +`pNext` member that can point to extension information in the future. We're +using value initialization here to leave it as `nullptr`. + +A lot of information in Vulkan is passed through structs instead of function +parameters and we'll have to fill in one more struct to provide sufficient +information for creating an instance. This next struct is not optional and tells +the Vulkan driver which global extensions and validation layers we want to use. +Global here means that they apply to the entire program and not a specific +device, which will become clear in the next few chapters. + +```c++ +VkInstanceCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; +createInfo.pApplicationInfo = &appInfo; +``` + +The first two parameters are straightforward. The next two layers specify the +desired global extensions. As mentioned in the overview chapter, Vulkan is a +platform agnostic API, which means that you need an extension to interface with +the window system. GLFW has a handy built-in function that returns the +extension(s) it needs to do that which we can pass to the struct: + +```c++ +uint32_t glfwExtensionCount = 0; +const char** glfwExtensions; + +glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + +createInfo.enabledExtensionCount = glfwExtensionCount; +createInfo.ppEnabledExtensionNames = glfwExtensions; +``` + +The last two members of the struct determine the global validation layers to +enable. We'll talk about these more in-depth in the next chapter, so just leave +these empty for now. + +```c++ +createInfo.enabledLayerCount = 0; +``` + +We've now specified everything Vulkan needs to create an instance and we can +finally issue the `vkCreateInstance` call: + +```c++ +VkResult result = vkCreateInstance(&createInfo, nullptr, &instance); +``` + +As you'll see, the general pattern that object creation function parameters in +Vulkan follow is: + +* Pointer to struct with creation info +* Pointer to custom allocator callbacks, always `nullptr` in this tutorial +* Pointer to the variable that stores the handle to the new object + +If everything went well then the handle to the instance was stored in the +`VkInstance` class member. Nearly all Vulkan functions return a value of type +`VkResult` that is either `VK_SUCCESS` or an error code. To check if the +instance was created successfully, we don't need to store the result and can +just use a check for the success value instead: + +```c++ +if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); +} +``` + +Now run the program to make sure that the instance is created successfully. + +## Encountered VK_ERROR_INCOMPATIBLE_DRIVER: +If using MacOS with the latest MoltenVK sdk, you may get `VK_ERROR_INCOMPATIBLE_DRIVER` +returned from `vkCreateInstance`. According to the [Getting Start Notes](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html). Beginning with the 1.3.216 Vulkan SDK, the `VK_KHR_PORTABILITY_subset` +extension is mandatory. + +To get over this error, first add the `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` bit +to `VkInstanceCreateInfo` struct's flags, then add `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME` +to instance enabled extension list. + +Typically the code could be like this: +```c++ +... + +std::vector requiredExtensions; + +for(uint32_t i = 0; i < glfwExtensionCount; i++) { + requiredExtensions.emplace_back(glfwExtensions[i]); +} + +requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + +createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + +createInfo.enabledExtensionCount = (uint32_t) requiredExtensions.size(); +createInfo.ppEnabledExtensionNames = requiredExtensions.data(); + +if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); +} +``` + +## Checking for extension support + +If you look at the `vkCreateInstance` documentation then you'll see that one of +the possible error codes is `VK_ERROR_EXTENSION_NOT_PRESENT`. We could simply +specify the extensions we require and terminate if that error code comes back. +That makes sense for essential extensions like the window system interface, but +what if we want to check for optional functionality? + +To retrieve a list of supported extensions before creating an instance, there's +the `vkEnumerateInstanceExtensionProperties` function. It takes a pointer to a +variable that stores the number of extensions and an array of +`VkExtensionProperties` to store details of the extensions. It also takes an +optional first parameter that allows us to filter extensions by a specific +validation layer, which we'll ignore for now. + +To allocate an array to hold the extension details we first need to know how +many there are. You can request just the number of extensions by leaving the +latter parameter empty: + +```c++ +uint32_t extensionCount = 0; +vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); +``` + +Now allocate an array to hold the extension details (`include `): + +```c++ +std::vector extensions(extensionCount); +``` + +Finally we can query the extension details: + +```c++ +vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); +``` + +Each `VkExtensionProperties` struct contains the name and version of an +extension. We can list them with a simple for loop (`\t` is a tab for +indentation): + +```c++ +std::cout << "available extensions:\n"; + +for (const auto& extension : extensions) { + std::cout << '\t' << extension.extensionName << '\n'; +} +``` + +You can add this code to the `createInstance` function if you'd like to provide +some details about the Vulkan support. As a challenge, try to create a function +that checks if all of the extensions returned by +`glfwGetRequiredInstanceExtensions` are included in the supported extensions +list. + +## Cleaning up + +The `VkInstance` should be only destroyed right before the program exits. It can +be destroyed in `cleanup` with the `vkDestroyInstance` function: + +```c++ +void cleanup() { + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +The parameters for the `vkDestroyInstance` function are straightforward. As +mentioned in the previous chapter, the allocation and deallocation functions +in Vulkan have an optional allocator callback that we'll ignore by passing +`nullptr` to it. All of the other Vulkan resources that we'll create in the +following chapters should be cleaned up before the instance is destroyed. + +Before continuing with the more complex steps after instance creation, it's time +to evaluate our debugging options by checking out [validation layers](!en/Drawing_a_triangle/Setup/Validation_layers). + +[C++ code](/code/01_instance_creation.cpp) diff --git a/en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md b/en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md new file mode 100644 index 00000000..569a0178 --- /dev/null +++ b/en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md @@ -0,0 +1,458 @@ +## What are validation layers? + +The Vulkan API is designed around the idea of minimal driver overhead and one of +the manifestations of that goal is that there is very limited error checking in +the API by default. Even mistakes as simple as setting enumerations to incorrect +values or passing null pointers to required parameters are generally not +explicitly handled and will simply result in crashes or undefined behavior. +Because Vulkan requires you to be very explicit about everything you're doing, +it's easy to make many small mistakes like using a new GPU feature and +forgetting to request it at logical device creation time. + +However, that doesn't mean that these checks can't be added to the API. Vulkan +introduces an elegant system for this known as *validation layers*. Validation +layers are optional components that hook into Vulkan function calls to apply +additional operations. Common operations in validation layers are: + +* Checking the values of parameters against the specification to detect misuse +* Tracking creation and destruction of objects to find resource leaks +* Checking thread safety by tracking the threads that calls originate from +* Logging every call and its parameters to the standard output +* Tracing Vulkan calls for profiling and replaying + +Here's an example of what the implementation of a function in a diagnostics +validation layer could look like: + +```c++ +VkResult vkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* instance) { + + if (pCreateInfo == nullptr || instance == nullptr) { + log("Null pointer passed to required parameter!"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + return real_vkCreateInstance(pCreateInfo, pAllocator, instance); +} +``` + +These validation layers can be freely stacked to include all the debugging +functionality that you're interested in. You can simply enable validation layers +for debug builds and completely disable them for release builds, which gives you +the best of both worlds! + +Vulkan does not come with any validation layers built-in, but the LunarG Vulkan +SDK provides a nice set of layers that check for common errors. They're also +completely [open source](https://github.com/KhronosGroup/Vulkan-ValidationLayers), +so you can check which kind of mistakes they check for and contribute. Using the +validation layers is the best way to avoid your application breaking on +different drivers by accidentally relying on undefined behavior. + +Validation layers can only be used if they have been installed onto the system. +For example, the LunarG validation layers are only available on PCs with the +Vulkan SDK installed. + +There were formerly two different types of validation layers in Vulkan: instance +and device specific. The idea was that instance layers would only check +calls related to global Vulkan objects like instances, and device specific layers +would only check calls related to a specific GPU. Device specific layers have now been +deprecated, which means that instance validation layers apply to all Vulkan +calls. The specification document still recommends that you enable validation +layers at device level as well for compatibility, which is required by some +implementations. We'll simply specify the same layers as the instance at logical +device level, which we'll see [later on](!en/Drawing_a_triangle/Setup/Logical_device_and_queues). + +## Using validation layers + +In this section we'll see how to enable the standard diagnostics layers provided +by the Vulkan SDK. Just like extensions, validation layers need to be enabled by +specifying their name. All of the useful standard validation is bundled into a layer included in the SDK that is known as `VK_LAYER_KHRONOS_validation`. + +Let's first add two configuration variables to the program to specify the layers +to enable and whether to enable them or not. I've chosen to base that value on +whether the program is being compiled in debug mode or not. The `NDEBUG` macro +is part of the C++ standard and means "not debug". + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG + const bool enableValidationLayers = false; +#else + const bool enableValidationLayers = true; +#endif +``` + +We'll add a new function `checkValidationLayerSupport` that checks if all of +the requested layers are available. First list all of the available layers +using the `vkEnumerateInstanceLayerProperties` function. Its usage is identical +to that of `vkEnumerateInstanceExtensionProperties` which was discussed in the +instance creation chapter. + +```c++ +bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + return false; +} +``` + +Next, check if all of the layers in `validationLayers` exist in the +`availableLayers` list. You may need to include `` for `strcmp`. + +```c++ +for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } +} + +return true; +``` + +We can now use this function in `createInstance`: + +```c++ +void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + ... +} +``` + +Now run the program in debug mode and ensure that the error does not occur. If +it does, then have a look at the FAQ. + +Finally, modify the `VkInstanceCreateInfo` struct instantiation to include the +validation layer names if they are enabled: + +```c++ +if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); +} else { + createInfo.enabledLayerCount = 0; +} +``` + +If the check was successful then `vkCreateInstance` should not ever return a +`VK_ERROR_LAYER_NOT_PRESENT` error, but you should run the program to make sure. + +## Message callback + +The validation layers will print debug messages to the standard output by default, but we can also handle them ourselves by providing an explicit callback in our program. This will also allow you to decide which kind of messages you would like to see, because not all are necessarily (fatal) errors. If you don't want to do that right now then you may skip to the last section in this chapter. + +To set up a callback in the program to handle messages and the associated details, we have to set up a debug messenger with a callback using the `VK_EXT_debug_utils` extension. + +We'll first create a `getRequiredExtensions` function that will return the +required list of extensions based on whether validation layers are enabled or +not: + +```c++ +std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; +} +``` + +The extensions specified by GLFW are always required, but the debug messenger +extension is conditionally added. Note that I've used the +`VK_EXT_DEBUG_UTILS_EXTENSION_NAME` macro here which is equal to the literal +string "VK_EXT_debug_utils". Using this macro lets you avoid typos. + +We can now use this function in `createInstance`: + +```c++ +auto extensions = getRequiredExtensions(); +createInfo.enabledExtensionCount = static_cast(extensions.size()); +createInfo.ppEnabledExtensionNames = extensions.data(); +``` + +Run the program to make sure you don't receive a +`VK_ERROR_EXTENSION_NOT_PRESENT` error. We don't really need to check for the +existence of this extension, because it should be implied by the availability of +the validation layers. + +Now let's see what a debug callback function looks like. Add a new static member +function called `debugCallback` with the `PFN_vkDebugUtilsMessengerCallbackEXT` +prototype. The `VKAPI_ATTR` and `VKAPI_CALL` ensure that the function has the +right signature for Vulkan to call it. + +```c++ +static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; +} +``` + +The first parameter specifies the severity of the message, which is one of the following flags: + +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT`: Diagnostic message +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`: Informational message like the creation of a resource +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT`: Message about behavior that is not necessarily an error, but very likely a bug in your application +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT`: Message about behavior that is invalid and may cause crashes + +The values of this enumeration are set up in such a way that you can use a comparison operation to check if a message is equal or worse compared to some level of severity, for example: + +```c++ +if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + // Message is important enough to show +} +``` + +The `messageType` parameter can have the following values: + +* `VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT`: Some event has happened that is unrelated to the specification or performance +* `VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT`: Something has happened that violates the specification or indicates a possible mistake +* `VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT`: Potential non-optimal use of Vulkan + +The `pCallbackData` parameter refers to a `VkDebugUtilsMessengerCallbackDataEXT` struct containing the details of the message itself, with the most important members being: + +* `pMessage`: The debug message as a null-terminated string +* `pObjects`: Array of Vulkan object handles related to the message +* `objectCount`: Number of objects in array + +Finally, the `pUserData` parameter contains a pointer that was specified during the setup of the callback and allows you to pass your own data to it. + +The callback returns a boolean that indicates if the Vulkan call that triggered +the validation layer message should be aborted. If the callback returns true, +then the call is aborted with the `VK_ERROR_VALIDATION_FAILED_EXT` error. This +is normally only used to test the validation layers themselves, so you should +always return `VK_FALSE`. + +All that remains now is telling Vulkan about the callback function. Perhaps +somewhat surprisingly, even the debug callback in Vulkan is managed with a +handle that needs to be explicitly created and destroyed. Such a callback is part of a *debug messenger* and you can have as many of them as you want. Add a class member for +this handle right under `instance`: + +```c++ +VkDebugUtilsMessengerEXT debugMessenger; +``` + +Now add a function `setupDebugMessenger` to be called from `initVulkan` right +after `createInstance`: + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); +} + +void setupDebugMessenger() { + if (!enableValidationLayers) return; + +} +``` + +We'll need to fill in a structure with details about the messenger and its callback: + +```c++ +VkDebugUtilsMessengerCreateInfoEXT createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; +createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; +createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; +createInfo.pfnUserCallback = debugCallback; +createInfo.pUserData = nullptr; // Optional +``` + +The `messageSeverity` field allows you to specify all the types of severities you would like your callback to be called for. I've specified all types except for `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT` here to receive notifications about possible problems while leaving out verbose general debug info. + +Similarly the `messageType` field lets you filter which types of messages your callback is notified about. I've simply enabled all types here. You can always disable some if they're not useful to you. + +Finally, the `pfnUserCallback` field specifies the pointer to the callback function. You can optionally pass a pointer to the `pUserData` field which will be passed along to the callback function via the `pUserData` parameter. You could use this to pass a pointer to the `HelloTriangleApplication` class, for example. + +Note that there are many more ways to configure validation layer messages and debug callbacks, but this is a good setup to get started with for this tutorial. See the [extension specification](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap50.html#VK_EXT_debug_utils) for more info about the possibilities. + +This struct should be passed to the `vkCreateDebugUtilsMessengerEXT` function to +create the `VkDebugUtilsMessengerEXT` object. Unfortunately, because this +function is an extension function, it is not automatically loaded. We have to +look up its address ourselves using `vkGetInstanceProcAddr`. We're going to +create our own proxy function that handles this in the background. I've added it +right above the `HelloTriangleApplication` class definition. + +```c++ +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} +``` + +The `vkGetInstanceProcAddr` function will return `nullptr` if the function +couldn't be loaded. We can now call this function to create the extension +object if it's available: + +```c++ +if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); +} +``` + +The second to last parameter is again the optional allocator callback that we +set to `nullptr`, other than that the parameters are fairly straightforward. +Since the debug messenger is specific to our Vulkan instance and its layers, it +needs to be explicitly specified as first argument. You will also see this +pattern with other *child* objects later on. + +The `VkDebugUtilsMessengerEXT` object also needs to be cleaned up with a call to +`vkDestroyDebugUtilsMessengerEXT`. Similarly to `vkCreateDebugUtilsMessengerEXT` +the function needs to be explicitly loaded. + +Create another proxy function right below `CreateDebugUtilsMessengerEXT`: + +```c++ +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} +``` + +Make sure that this function is either a static class function or a function +outside the class. We can then call it in the `cleanup` function: + +```c++ +void cleanup() { + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +## Debugging instance creation and destruction + +Although we've now added debugging with validation layers to the program we're not covering everything quite yet. The `vkCreateDebugUtilsMessengerEXT` call requires a valid instance to have been created and `vkDestroyDebugUtilsMessengerEXT` must be called before the instance is destroyed. This currently leaves us unable to debug any issues in the `vkCreateInstance` and `vkDestroyInstance` calls. + +However, if you closely read the [extension documentation](https://github.com/KhronosGroup/Vulkan-Docs/blob/main/appendices/VK_EXT_debug_utils.adoc#examples), you'll see that there is a way to create a separate debug utils messenger specifically for those two function calls. It requires you to simply pass a pointer to a `VkDebugUtilsMessengerCreateInfoEXT` struct in the `pNext` extension field of `VkInstanceCreateInfo`. First extract population of the messenger create info into a separate function: + +```c++ +void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; +} + +... + +void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } +} +``` + +We can now re-use this in the `createInstance` function: + +```c++ +void createInstance() { + ... + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + ... + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } +} +``` + +The `debugCreateInfo` variable is placed outside the if statement to ensure that it is not destroyed before the `vkCreateInstance` call. By creating an additional debug messenger this way it will automatically be used during `vkCreateInstance` and `vkDestroyInstance` and cleaned up after that. + +## Testing + +Now let's intentionally make a mistake to see the validation layers in action. Temporarily remove the call to `DestroyDebugUtilsMessengerEXT` in the `cleanup` function and run your program. Once it exits you should see something like this: + +![](/images/validation_layer_test.png) + +>If you don't see any messages then [check your installation](https://vulkan.lunarg.com/doc/view/1.2.131.1/windows/getting_started.html#user-content-verify-the-installation). + +If you want to see which call triggered a message, you can add a breakpoint to the message callback and look at the stack trace. + +## Configuration + +There are a lot more settings for the behavior of validation layers than just +the flags specified in the `VkDebugUtilsMessengerCreateInfoEXT` struct. Browse +to the Vulkan SDK and go to the `Config` directory. There you will find a +`vk_layer_settings.txt` file that explains how to configure the layers. + +To configure the layer settings for your own application, copy the file to the +`Debug` and `Release` directories of your project and follow the instructions to +set the desired behavior. However, for the remainder of this tutorial I'll +assume that you're using the default settings. + +Throughout this tutorial I'll be making a couple of intentional mistakes to show +you how helpful the validation layers are with catching them and to teach you +how important it is to know exactly what you're doing with Vulkan. Now it's time +to look at [Vulkan devices in the system](!en/Drawing_a_triangle/Setup/Physical_devices_and_queue_families). + +[C++ code](/code/02_validation_layers.cpp) diff --git a/en/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md b/en/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md new file mode 100644 index 00000000..5761b9bc --- /dev/null +++ b/en/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md @@ -0,0 +1,364 @@ +## Selecting a physical device + +After initializing the Vulkan library through a VkInstance we need to look for +and select a graphics card in the system that supports the features we need. In +fact we can select any number of graphics cards and use them simultaneously, but +in this tutorial we'll stick to the first graphics card that suits our needs. + +We'll add a function `pickPhysicalDevice` and add a call to it in the +`initVulkan` function. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + pickPhysicalDevice(); +} + +void pickPhysicalDevice() { + +} +``` + +The graphics card that we'll end up selecting will be stored in a +VkPhysicalDevice handle that is added as a new class member. This object will be +implicitly destroyed when the VkInstance is destroyed, so we won't need to do +anything new in the `cleanup` function. + +```c++ +VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; +``` + +Listing the graphics cards is very similar to listing extensions and starts with +querying just the number. + +```c++ +uint32_t deviceCount = 0; +vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); +``` + +If there are 0 devices with Vulkan support then there is no point going further. + +```c++ +if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); +} +``` + +Otherwise we can now allocate an array to hold all of the VkPhysicalDevice +handles. + +```c++ +std::vector devices(deviceCount); +vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); +``` + +Now we need to evaluate each of them and check if they are suitable for the +operations we want to perform, because not all graphics cards are created equal. +For that we'll introduce a new function: + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + return true; +} +``` + +And we'll check if any of the physical devices meet the requirements that we'll +add to that function. + +```c++ +for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } +} + +if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); +} +``` + +The next section will introduce the first requirements that we'll check for in +the `isDeviceSuitable` function. As we'll start using more Vulkan features in +the later chapters we will also extend this function to include more checks. + +## Base device suitability checks + +To evaluate the suitability of a device we can start by querying for some +details. Basic device properties like the name, type and supported Vulkan +version can be queried using vkGetPhysicalDeviceProperties. + +```c++ +VkPhysicalDeviceProperties deviceProperties; +vkGetPhysicalDeviceProperties(device, &deviceProperties); +``` + +The support for optional features like texture compression, 64 bit floats and +multi viewport rendering (useful for VR) can be queried using +vkGetPhysicalDeviceFeatures: + +```c++ +VkPhysicalDeviceFeatures deviceFeatures; +vkGetPhysicalDeviceFeatures(device, &deviceFeatures); +``` + +There are more details that can be queried from devices that we'll discuss later +concerning device memory and queue families (see the next section). + +As an example, let's say we consider our application only usable for dedicated +graphics cards that support geometry shaders. Then the `isDeviceSuitable` +function would look like this: + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + VkPhysicalDeviceProperties deviceProperties; + VkPhysicalDeviceFeatures deviceFeatures; + vkGetPhysicalDeviceProperties(device, &deviceProperties); + vkGetPhysicalDeviceFeatures(device, &deviceFeatures); + + return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && + deviceFeatures.geometryShader; +} +``` + +Instead of just checking if a device is suitable or not and going with the first +one, you could also give each device a score and pick the highest one. That way +you could favor a dedicated graphics card by giving it a higher score, but fall +back to an integrated GPU if that's the only available one. You could implement +something like that as follows: + +```c++ +#include + +... + +void pickPhysicalDevice() { + ... + + // Use an ordered map to automatically sort candidates by increasing score + std::multimap candidates; + + for (const auto& device : devices) { + int score = rateDeviceSuitability(device); + candidates.insert(std::make_pair(score, device)); + } + + // Check if the best candidate is suitable at all + if (candidates.rbegin()->first > 0) { + physicalDevice = candidates.rbegin()->second; + } else { + throw std::runtime_error("failed to find a suitable GPU!"); + } +} + +int rateDeviceSuitability(VkPhysicalDevice device) { + ... + + int score = 0; + + // Discrete GPUs have a significant performance advantage + if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + score += 1000; + } + + // Maximum possible size of textures affects graphics quality + score += deviceProperties.limits.maxImageDimension2D; + + // Application can't function without geometry shaders + if (!deviceFeatures.geometryShader) { + return 0; + } + + return score; +} +``` + +You don't need to implement all that for this tutorial, but it's to give you an +idea of how you could design your device selection process. Of course you can +also just display the names of the choices and allow the user to select. + +Because we're just starting out, Vulkan support is the only thing we need and +therefore we'll settle for just any GPU: + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + return true; +} +``` + +In the next section we'll discuss the first real required feature to check for. + +## Queue families + +It has been briefly touched upon before that almost every operation in Vulkan, +anything from drawing to uploading textures, requires commands to be submitted +to a queue. There are different types of queues that originate from different +*queue families* and each family of queues allows only a subset of commands. For +example, there could be a queue family that only allows processing of compute +commands or one that only allows memory transfer related commands. + +We need to check which queue families are supported by the device and which one +of these supports the commands that we want to use. For that purpose we'll add a +new function `findQueueFamilies` that looks for all the queue families we need. + +Right now we are only going to look for a queue that supports graphics commands, +so the function could look like this: + +```c++ +uint32_t findQueueFamilies(VkPhysicalDevice device) { + // Logic to find graphics queue family +} +``` + +However, in one of the next chapters we're already going to look for yet another +queue, so it's better to prepare for that and bundle the indices into a struct: + +```c++ +struct QueueFamilyIndices { + uint32_t graphicsFamily; +}; + +QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + // Logic to find queue family indices to populate struct with + return indices; +} +``` + +But what if a queue family is not available? We could throw an exception in +`findQueueFamilies`, but this function is not really the right place to make +decisions about device suitability. For example, we may *prefer* devices with a +dedicated transfer queue family, but not require it. Therefore we need some way +of indicating whether a particular queue family was found. + +It's not really possible to use a magic value to indicate the nonexistence of a +queue family, since any value of `uint32_t` could in theory be a valid queue +family index including `0`. Luckily C++17 introduced a data structure to +distinguish between the case of a value existing or not: + +```c++ +#include + +... + +std::optional graphicsFamily; + +std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // false + +graphicsFamily = 0; + +std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // true +``` + +`std::optional` is a wrapper that contains no value until you assign something +to it. At any point you can query if it contains a value or not by calling its +`has_value()` member function. That means that we can change the logic to: + +```c++ +#include + +... + +struct QueueFamilyIndices { + std::optional graphicsFamily; +}; + +QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + // Assign index to queue families that could be found + return indices; +} +``` + +We can now begin to actually implement `findQueueFamilies`: + +```c++ +QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + ... + + return indices; +} +``` + +The process of retrieving the list of queue families is exactly what you expect +and uses `vkGetPhysicalDeviceQueueFamilyProperties`: + +```c++ +uint32_t queueFamilyCount = 0; +vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + +std::vector queueFamilies(queueFamilyCount); +vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); +``` + +The VkQueueFamilyProperties struct contains some details about the queue family, +including the type of operations that are supported and the number of queues +that can be created based on that family. We need to find at least one queue +family that supports `VK_QUEUE_GRAPHICS_BIT`. + +```c++ +int i = 0; +for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + i++; +} +``` + +Now that we have this fancy queue family lookup function, we can use it as a +check in the `isDeviceSuitable` function to ensure that the device can process +the commands we want to use: + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.graphicsFamily.has_value(); +} +``` + +To make this a little bit more convenient, we'll also add a generic check to the +struct itself: + +```c++ +struct QueueFamilyIndices { + std::optional graphicsFamily; + + bool isComplete() { + return graphicsFamily.has_value(); + } +}; + +... + +bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.isComplete(); +} +``` + +We can now also use this for an early exit from `findQueueFamilies`: + +```c++ +for (const auto& queueFamily : queueFamilies) { + ... + + if (indices.isComplete()) { + break; + } + + i++; +} +``` + +Great, that's all we need for now to find the right physical device! The next +step is to [create a logical device](!en/Drawing_a_triangle/Setup/Logical_device_and_queues) +to interface with it. + +[C++ code](/code/03_physical_device_selection.cpp) diff --git a/en/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md b/en/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md new file mode 100644 index 00000000..f2677d08 --- /dev/null +++ b/en/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md @@ -0,0 +1,171 @@ +## Introduction + +After selecting a physical device to use we need to set up a *logical device* to +interface with it. The logical device creation process is similar to the +instance creation process and describes the features we want to use. We also +need to specify which queues to create now that we've queried which queue +families are available. You can even create multiple logical devices from the +same physical device if you have varying requirements. + +Start by adding a new class member to store the logical device handle in. + +```c++ +VkDevice device; +``` + +Next, add a `createLogicalDevice` function that is called from `initVulkan`. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + pickPhysicalDevice(); + createLogicalDevice(); +} + +void createLogicalDevice() { + +} +``` + +## Specifying the queues to be created + +The creation of a logical device involves specifying a bunch of details in +structs again, of which the first one will be `VkDeviceQueueCreateInfo`. This +structure describes the number of queues we want for a single queue family. +Right now we're only interested in a queue with graphics capabilities. + +```c++ +QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + +VkDeviceQueueCreateInfo queueCreateInfo{}; +queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; +queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value(); +queueCreateInfo.queueCount = 1; +``` + +The currently available drivers will only allow you to create a small number of +queues for each queue family and you don't really need more than one. That's +because you can create all of the command buffers on multiple threads and then +submit them all at once on the main thread with a single low-overhead call. + +Vulkan lets you assign priorities to queues to influence the scheduling of +command buffer execution using floating point numbers between `0.0` and `1.0`. +This is required even if there is only a single queue: + +```c++ +float queuePriority = 1.0f; +queueCreateInfo.pQueuePriorities = &queuePriority; +``` + +## Specifying used device features + +The next information to specify is the set of device features that we'll be +using. These are the features that we queried support for with +`vkGetPhysicalDeviceFeatures` in the previous chapter, like geometry shaders. +Right now we don't need anything special, so we can simply define it and leave +everything to `VK_FALSE`. We'll come back to this structure once we're about to +start doing more interesting things with Vulkan. + +```c++ +VkPhysicalDeviceFeatures deviceFeatures{}; +``` + +## Creating the logical device + +With the previous two structures in place, we can start filling in the main +`VkDeviceCreateInfo` structure. + +```c++ +VkDeviceCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; +``` + +First add pointers to the queue creation info and device features structs: + +```c++ +createInfo.pQueueCreateInfos = &queueCreateInfo; +createInfo.queueCreateInfoCount = 1; + +createInfo.pEnabledFeatures = &deviceFeatures; +``` + +The remainder of the information bears a resemblance to the +`VkInstanceCreateInfo` struct and requires you to specify extensions and +validation layers. The difference is that these are device specific this time. + +An example of a device specific extension is `VK_KHR_swapchain`, which allows +you to present rendered images from that device to windows. It is possible that +there are Vulkan devices in the system that lack this ability, for example +because they only support compute operations. We will come back to this +extension in the swap chain chapter. + +Previous implementations of Vulkan made a distinction between instance and device specific validation layers, but this is [no longer the case](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap40.html#extendingvulkan-layers-devicelayerdeprecation). That means that the `enabledLayerCount` and `ppEnabledLayerNames` fields of `VkDeviceCreateInfo` are ignored by up-to-date implementations. However, it is still a good idea to set them anyway to be compatible with older implementations: + +```c++ +createInfo.enabledExtensionCount = 0; + +if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); +} else { + createInfo.enabledLayerCount = 0; +} +``` + +We won't need any device specific extensions for now. + +That's it, we're now ready to instantiate the logical device with a call to the +appropriately named `vkCreateDevice` function. + +```c++ +if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); +} +``` + +The parameters are the physical device to interface with, the queue and usage +info we just specified, the optional allocation callbacks pointer and a pointer +to a variable to store the logical device handle in. Similarly to the instance +creation function, this call can return errors based on enabling non-existent +extensions or specifying the desired usage of unsupported features. + +The device should be destroyed in `cleanup` with the `vkDestroyDevice` function: + +```c++ +void cleanup() { + vkDestroyDevice(device, nullptr); + ... +} +``` + +Logical devices don't interact directly with instances, which is why it's not +included as a parameter. + +## Retrieving queue handles + +The queues are automatically created along with the logical device, but we don't +have a handle to interface with them yet. First add a class member to store a +handle to the graphics queue: + +```c++ +VkQueue graphicsQueue; +``` + +Device queues are implicitly cleaned up when the device is destroyed, so we +don't need to do anything in `cleanup`. + +We can use the `vkGetDeviceQueue` function to retrieve queue handles for each +queue family. The parameters are the logical device, queue family, queue index +and a pointer to the variable to store the queue handle in. Because we're only +creating a single queue from this family, we'll simply use index `0`. + +```c++ +vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); +``` + +With the logical device and queue handles we can now actually start using the +graphics card to do things! In the next few chapters we'll set up the resources +to present results to the window system. + +[C++ code](/code/04_logical_device.cpp) diff --git a/en/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md b/en/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md new file mode 100644 index 00000000..966a8946 --- /dev/null +++ b/en/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md @@ -0,0 +1,233 @@ +Since Vulkan is a platform agnostic API, it can not interface directly with the +window system on its own. To establish the connection between Vulkan and the +window system to present results to the screen, we need to use the WSI (Window +System Integration) extensions. In this chapter we'll discuss the first one, +which is `VK_KHR_surface`. It exposes a `VkSurfaceKHR` object that represents an +abstract type of surface to present rendered images to. The surface in our +program will be backed by the window that we've already opened with GLFW. + +The `VK_KHR_surface` extension is an instance level extension and we've actually +already enabled it, because it's included in the list returned by +`glfwGetRequiredInstanceExtensions`. The list also includes some other WSI +extensions that we'll use in the next couple of chapters. + +The window surface needs to be created right after the instance creation, +because it can actually influence the physical device selection. The reason we +postponed this is because window surfaces are part of the larger topic of +render targets and presentation for which the explanation would have cluttered +the basic setup. It should also be noted that window surfaces are an entirely +optional component in Vulkan, if you just need off-screen rendering. Vulkan +allows you to do that without hacks like creating an invisible window +(necessary for OpenGL). + +## Window surface creation + +Start by adding a `surface` class member right below the debug callback. + +```c++ +VkSurfaceKHR surface; +``` + +Although the `VkSurfaceKHR` object and its usage is platform agnostic, its +creation isn't because it depends on window system details. For example, it +needs the `HWND` and `HMODULE` handles on Windows. Therefore there is a +platform-specific addition to the extension, which on Windows is called +`VK_KHR_win32_surface` and is also automatically included in the list from +`glfwGetRequiredInstanceExtensions`. + +I will demonstrate how this platform specific extension can be used to create a +surface on Windows, but we won't actually use it in this tutorial. It doesn't +make any sense to use a library like GLFW and then proceed to use +platform-specific code anyway. GLFW actually has `glfwCreateWindowSurface` that +handles the platform differences for us. Still, it's good to see what it does +behind the scenes before we start relying on it. + +To access native platform functions, you need to update the includes at the top: + +```c++ +#define VK_USE_PLATFORM_WIN32_KHR +#define GLFW_INCLUDE_VULKAN +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +``` + +Because a window surface is a Vulkan object, it comes with a +`VkWin32SurfaceCreateInfoKHR` struct that needs to be filled in. It has two +important parameters: `hwnd` and `hinstance`. These are the handles to the +window and the process. + +```c++ +VkWin32SurfaceCreateInfoKHR createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; +createInfo.hwnd = glfwGetWin32Window(window); +createInfo.hinstance = GetModuleHandle(nullptr); +``` + +The `glfwGetWin32Window` function is used to get the raw `HWND` from the GLFW +window object. The `GetModuleHandle` call returns the `HINSTANCE` handle of the +current process. + +After that the surface can be created with `vkCreateWin32SurfaceKHR`, which includes a parameter for the instance, surface creation details, custom allocators and the variable for the surface handle to be stored in. Technically this is a WSI extension function, but it is so commonly used that the standard Vulkan loader includes it, so unlike other extensions you don't need to explicitly load it. + +```c++ +if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); +} +``` + +The process is similar for other platforms like Linux, where +`vkCreateXcbSurfaceKHR` takes an XCB connection and window as creation details +with X11. + +The `glfwCreateWindowSurface` function performs exactly this operation with a +different implementation for each platform. We'll now integrate it into our +program. Add a function `createSurface` to be called from `initVulkan` right +after instance creation and `setupDebugMessenger`. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); +} + +void createSurface() { + +} +``` + +The GLFW call takes simple parameters instead of a struct which makes the +implementation of the function very straightforward: + +```c++ +void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } +} +``` + +The parameters are the `VkInstance`, GLFW window pointer, custom allocators and +pointer to `VkSurfaceKHR` variable. It simply passes through the `VkResult` from +the relevant platform call. GLFW doesn't offer a special function for destroying +a surface, but that can easily be done through the original API: + +```c++ +void cleanup() { + ... + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + ... + } +``` + +Make sure that the surface is destroyed before the instance. + +## Querying for presentation support + +Although the Vulkan implementation may support window system integration, that +does not mean that every device in the system supports it. Therefore we need to +extend `isDeviceSuitable` to ensure that a device can present images to the +surface we created. Since the presentation is a queue-specific feature, the +problem is actually about finding a queue family that supports presenting to the +surface we created. + +It's actually possible that the queue families supporting drawing commands and +the ones supporting presentation do not overlap. Therefore we have to take into +account that there could be a distinct presentation queue by modifying the +`QueueFamilyIndices` structure: + +```c++ +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; +``` + +Next, we'll modify the `findQueueFamilies` function to look for a queue family +that has the capability of presenting to our window surface. The function to +check for that is `vkGetPhysicalDeviceSurfaceSupportKHR`, which takes the +physical device, queue family index and surface as parameters. Add a call to it +in the same loop as the `VK_QUEUE_GRAPHICS_BIT`: + +```c++ +VkBool32 presentSupport = false; +vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); +``` + +Then simply check the value of the boolean and store the presentation family +queue index: + +```c++ +if (presentSupport) { + indices.presentFamily = i; +} +``` + +Note that it's very likely that these end up being the same queue family after +all, but throughout the program we will treat them as if they were separate +queues for a uniform approach. Nevertheless, you could add logic to explicitly +prefer a physical device that supports drawing and presentation in the same +queue for improved performance. + +## Creating the presentation queue + +The one thing that remains is modifying the logical device creation procedure to +create the presentation queue and retrieve the `VkQueue` handle. Add a member +variable for the handle: + +```c++ +VkQueue presentQueue; +``` + +Next, we need to have multiple `VkDeviceQueueCreateInfo` structs to create a +queue from both families. An elegant way to do that is to create a set of all +unique queue families that are necessary for the required queues: + +```c++ +#include + +... + +QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + +std::vector queueCreateInfos; +std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + +float queuePriority = 1.0f; +for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); +} +``` + +And modify `VkDeviceCreateInfo` to point to the vector: + +```c++ +createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); +createInfo.pQueueCreateInfos = queueCreateInfos.data(); +``` + +If the queue families are the same, then we only need to pass its index once. +Finally, add a call to retrieve the queue handle: + +```c++ +vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); +``` + +In case the queue families are the same, the two handles will most likely have +the same value now. In the next chapter we're going to look at swap chains and +how they give us the ability to present images to the surface. + +[C++ code](/code/05_window_surface.cpp) diff --git a/en/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md b/en/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md new file mode 100644 index 00000000..f593b5a6 --- /dev/null +++ b/en/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md @@ -0,0 +1,603 @@ +Vulkan does not have the concept of a "default framebuffer", hence it requires an infrastructure that will own the buffers we will render to before we visualize them on the screen. This infrastructure is +known as the *swap chain* and must be created explicitly in Vulkan. The swap +chain is essentially a queue of images that are waiting to be presented to the +screen. Our application will acquire such an image to draw to it, and then +return it to the queue. How exactly the queue works and the conditions for +presenting an image from the queue depend on how the swap chain is set up, but +the general purpose of the swap chain is to synchronize the presentation of +images with the refresh rate of the screen. + +## Checking for swap chain support + +Not all graphics cards are capable of presenting images directly to a screen for +various reasons, for example because they are designed for servers and don't +have any display outputs. Secondly, since image presentation is heavily tied +into the window system and the surfaces associated with windows, it is not +actually part of the Vulkan core. You have to enable the `VK_KHR_swapchain` +device extension after querying for its support. + +For that purpose we'll first extend the `isDeviceSuitable` function to check if +this extension is supported. We've previously seen how to list the extensions +that are supported by a `VkPhysicalDevice`, so doing that should be fairly +straightforward. Note that the Vulkan header file provides a nice macro +`VK_KHR_SWAPCHAIN_EXTENSION_NAME` that is defined as `VK_KHR_swapchain`. The +advantage of using this macro is that the compiler will catch misspellings. + +First declare a list of required device extensions, similar to the list of +validation layers to enable. + +```c++ +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; +``` + +Next, create a new function `checkDeviceExtensionSupport` that is called from +`isDeviceSuitable` as an additional check: + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + return indices.isComplete() && extensionsSupported; +} + +bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + return true; +} +``` + +Modify the body of the function to enumerate the extensions and check if all of +the required extensions are amongst them. + +```c++ +bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); +} +``` + +I've chosen to use a set of strings here to represent the unconfirmed required +extensions. That way we can easily tick them off while enumerating the sequence +of available extensions. Of course you can also use a nested loop like in +`checkValidationLayerSupport`. The performance difference is irrelevant. Now run +the code and verify that your graphics card is indeed capable of creating a +swap chain. It should be noted that the availability of a presentation queue, +as we checked in the previous chapter, implies that the swap chain extension +must be supported. However, it's still good to be explicit about things, and +the extension does have to be explicitly enabled. + +## Enabling device extensions + +Using a swapchain requires enabling the `VK_KHR_swapchain` extension first. +Enabling the extension just requires a small change to the logical device +creation structure: + +```c++ +createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); +createInfo.ppEnabledExtensionNames = deviceExtensions.data(); +``` + +Make sure to replace the existing line `createInfo.enabledExtensionCount = 0;` when you do so. + +## Querying details of swap chain support + +Just checking if a swap chain is available is not sufficient, because it may not +actually be compatible with our window surface. Creating a swap chain also +involves a lot more settings than instance and device creation, so we need to +query for some more details before we're able to proceed. + +There are basically three kinds of properties we need to check: + +* Basic surface capabilities (min/max number of images in swap chain, min/max +width and height of images) +* Surface formats (pixel format, color space) +* Available presentation modes + +Similar to `findQueueFamilies`, we'll use a struct to pass these details around +once they've been queried. The three aforementioned types of properties come in +the form of the following structs and lists of structs: + +```c++ +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; +``` + +We'll now create a new function `querySwapChainSupport` that will populate this +struct. + +```c++ +SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + return details; +} +``` + +This section covers how to query the structs that include this information. The +meaning of these structs and exactly which data they contain is discussed in the +next section. + +Let's start with the basic surface capabilities. These properties are simple to +query and are returned into a single `VkSurfaceCapabilitiesKHR` struct. + +```c++ +vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); +``` + +This function takes the specified `VkPhysicalDevice` and `VkSurfaceKHR` window +surface into account when determining the supported capabilities. All of the +support querying functions have these two as first parameters because they are +the core components of the swap chain. + +The next step is about querying the supported surface formats. Because this is a +list of structs, it follows the familiar ritual of 2 function calls: + +```c++ +uint32_t formatCount; +vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + +if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); +} +``` + +Make sure that the vector is resized to hold all the available formats. And +finally, querying the supported presentation modes works exactly the same way +with `vkGetPhysicalDeviceSurfacePresentModesKHR`: + +```c++ +uint32_t presentModeCount; +vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + +if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); +} +``` + +All of the details are in the struct now, so let's extend `isDeviceSuitable` +once more to utilize this function to verify that swap chain support is +adequate. Swap chain support is sufficient for this tutorial if there is at +least one supported image format and one supported presentation mode given the +window surface we have. + +```c++ +bool swapChainAdequate = false; +if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); +} +``` + +It is important that we only try to query for swap chain support after verifying +that the extension is available. The last line of the function changes to: + +```c++ +return indices.isComplete() && extensionsSupported && swapChainAdequate; +``` + +## Choosing the right settings for the swap chain + +If the `swapChainAdequate` conditions were met then the support is definitely +sufficient, but there may still be many different modes of varying optimality. +We'll now write a couple of functions to find the right settings for the best +possible swap chain. There are three types of settings to determine: + +* Surface format (color depth) +* Presentation mode (conditions for "swapping" images to the screen) +* Swap extent (resolution of images in swap chain) + +For each of these settings we'll have an ideal value in mind that we'll go with +if it's available and otherwise we'll create some logic to find the next best +thing. + +### Surface format + +The function for this setting starts out like this. We'll later pass the +`formats` member of the `SwapChainSupportDetails` struct as argument. + +```c++ +VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + +} +``` + +Each `VkSurfaceFormatKHR` entry contains a `format` and a `colorSpace` member. The +`format` member specifies the color channels and types. For example, +`VK_FORMAT_B8G8R8A8_SRGB` means that we store the B, G, R and alpha channels in +that order with an 8 bit unsigned integer for a total of 32 bits per pixel. The +`colorSpace` member indicates if the SRGB color space is supported or not using +the `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR` flag. Note that this flag used to be +called `VK_COLORSPACE_SRGB_NONLINEAR_KHR` in old versions of the specification. + +For the color space we'll use SRGB if it is available, because it [results in more accurate perceived colors](http://stackoverflow.com/questions/12524623/). It is also pretty much the standard color space for images, like the textures we'll use later on. +Because of that we should also use an SRGB color format, of which one of the most common ones is `VK_FORMAT_B8G8R8A8_SRGB`. + +Let's go through the list and see if the preferred combination is available: + +```c++ +for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } +} +``` + +If that also fails then we could start ranking the available formats based on +how "good" they are, but in most cases it's okay to just settle with the first +format that is specified. + +```c++ +VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; +} +``` + +### Presentation mode + +The presentation mode is arguably the most important setting for the swap chain, +because it represents the actual conditions for showing images to the screen. +There are four possible modes available in Vulkan: + +* `VK_PRESENT_MODE_IMMEDIATE_KHR`: Images submitted by your application are +transferred to the screen right away, which may result in tearing. +* `VK_PRESENT_MODE_FIFO_KHR`: The swap chain is a queue where the display takes +an image from the front of the queue when the display is refreshed and the +program inserts rendered images at the back of the queue. If the queue is full +then the program has to wait. This is most similar to vertical sync as found in +modern games. The moment that the display is refreshed is known as "vertical +blank". +* `VK_PRESENT_MODE_FIFO_RELAXED_KHR`: This mode only differs from the previous +one if the application is late and the queue was empty at the last vertical +blank. Instead of waiting for the next vertical blank, the image is transferred +right away when it finally arrives. This may result in visible tearing. +* `VK_PRESENT_MODE_MAILBOX_KHR`: This is another variation of the second mode. +Instead of blocking the application when the queue is full, the images that are +already queued are simply replaced with the newer ones. This mode can be used to +render frames as fast as possible while still avoiding tearing, resulting in fewer latency issues than standard vertical sync. This is commonly known as "triple buffering", although the existence of three buffers alone does not necessarily mean that the framerate is unlocked. + +Only the `VK_PRESENT_MODE_FIFO_KHR` mode is guaranteed to be available, so we'll +again have to write a function that looks for the best mode that is available: + +```c++ +VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + return VK_PRESENT_MODE_FIFO_KHR; +} +``` + +I personally think that `VK_PRESENT_MODE_MAILBOX_KHR` is a very nice trade-off if energy usage is not a concern. It allows us to avoid tearing while still maintaining a fairly low latency by rendering new images that are as up-to-date as possible right until the vertical blank. On mobile devices, where energy usage is more important, you will probably want to use `VK_PRESENT_MODE_FIFO_KHR` instead. Now, let's look through the list to see if `VK_PRESENT_MODE_MAILBOX_KHR` is available: + +```c++ +VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} +``` + +### Swap extent + +That leaves only one major property, for which we'll add one last function: + +```c++ +VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + +} +``` + +The swap extent is the resolution of the swap chain images and it's almost +always exactly equal to the resolution of the window that we're drawing to _in +pixels_ (more on that in a moment). The range of the possible resolutions is +defined in the `VkSurfaceCapabilitiesKHR` structure. Vulkan tells us to match +the resolution of the window by setting the width and height in the +`currentExtent` member. However, some window managers do allow us to differ here +and this is indicated by setting the width and height in `currentExtent` to a +special value: the maximum value of `uint32_t`. In that case we'll pick the +resolution that best matches the window within the `minImageExtent` and +`maxImageExtent` bounds. But we must specify the resolution in the correct unit. + +GLFW uses two units when measuring sizes: pixels and +[screen coordinates](https://www.glfw.org/docs/latest/intro_guide.html#coordinate_systems). +For example, the resolution `{WIDTH, HEIGHT}` that we specified earlier when +creating the window is measured in screen coordinates. But Vulkan works with +pixels, so the swap chain extent must be specified in pixels as well. +Unfortunately, if you are using a high DPI display (like Apple's Retina +display), screen coordinates don't correspond to pixels. Instead, due to the +higher pixel density, the resolution of the window in pixel will be larger than +the resolution in screen coordinates. So if Vulkan doesn't fix the swap extent +for us, we can't just use the original `{WIDTH, HEIGHT}`. Instead, we must use +`glfwGetFramebufferSize` to query the resolution of the window in pixel before +matching it against the minimum and maximum image extent. + +```c++ +#include // Necessary for uint32_t +#include // Necessary for std::numeric_limits +#include // Necessary for std::clamp + +... + +VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } +} +``` + +The `clamp` function is used here to bound the values of `width` and `height` between the allowed minimum and maximum extents that are supported by the implementation. + +## Creating the swap chain + +Now that we have all of these helper functions assisting us with the choices we +have to make at runtime, we finally have all the information that is needed to +create a working swap chain. + +Create a `createSwapChain` function that starts out with the results of these +calls and make sure to call it from `initVulkan` after logical device creation. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); +} + +void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); +} +``` + +Aside from these properties we also have to decide how many images we would like to have in the swap chain. The implementation specifies the minimum number that it requires to function: + +```c++ +uint32_t imageCount = swapChainSupport.capabilities.minImageCount; +``` + +However, simply sticking to this minimum means that we may sometimes have to wait on the driver to complete internal operations before we can acquire another image to render to. Therefore it is recommended to request at least one more image than the minimum: + +```c++ +uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; +``` + +We should also make sure to not exceed the maximum number of images while doing this, where `0` is a special value that means that there is no maximum: + +```c++ +if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; +} +``` + +As is tradition with Vulkan objects, creating the swap chain object requires +filling in a large structure. It starts out very familiarly: + +```c++ +VkSwapchainCreateInfoKHR createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; +createInfo.surface = surface; +``` + +After specifying which surface the swap chain should be tied to, the details of +the swap chain images are specified: + +```c++ +createInfo.minImageCount = imageCount; +createInfo.imageFormat = surfaceFormat.format; +createInfo.imageColorSpace = surfaceFormat.colorSpace; +createInfo.imageExtent = extent; +createInfo.imageArrayLayers = 1; +createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +``` + +The `imageArrayLayers` specifies the amount of layers each image consists of. +This is always `1` unless you are developing a stereoscopic 3D application. The +`imageUsage` bit field specifies what kind of operations we'll use the images in +the swap chain for. In this tutorial we're going to render directly to them, +which means that they're used as color attachment. It is also possible that +you'll render images to a separate image first to perform operations like +post-processing. In that case you may use a value like +`VK_IMAGE_USAGE_TRANSFER_DST_BIT` instead and use a memory operation to transfer +the rendered image to a swap chain image. + +```c++ +QueueFamilyIndices indices = findQueueFamilies(physicalDevice); +uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + +if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; +} else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; // Optional + createInfo.pQueueFamilyIndices = nullptr; // Optional +} +``` + +Next, we need to specify how to handle swap chain images that will be used +across multiple queue families. That will be the case in our application if the +graphics queue family is different from the presentation queue. We'll be drawing +on the images in the swap chain from the graphics queue and then submitting them +on the presentation queue. There are two ways to handle images that are +accessed from multiple queues: + +* `VK_SHARING_MODE_EXCLUSIVE`: An image is owned by one queue family at a time +and ownership must be explicitly transferred before using it in another queue +family. This option offers the best performance. +* `VK_SHARING_MODE_CONCURRENT`: Images can be used across multiple queue +families without explicit ownership transfers. + +If the queue families differ, then we'll be using the concurrent mode in this +tutorial to avoid having to do the ownership chapters, because these involve +some concepts that are better explained at a later time. Concurrent mode +requires you to specify in advance between which queue families ownership will +be shared using the `queueFamilyIndexCount` and `pQueueFamilyIndices` +parameters. If the graphics queue family and presentation queue family are the +same, which will be the case on most hardware, then we should stick to exclusive +mode, because concurrent mode requires you to specify at least two distinct +queue families. + +```c++ +createInfo.preTransform = swapChainSupport.capabilities.currentTransform; +``` + +We can specify that a certain transform should be applied to images in the swap +chain if it is supported (`supportedTransforms` in `capabilities`), like a 90 +degree clockwise rotation or horizontal flip. To specify that you do not want +any transformation, simply specify the current transformation. + +```c++ +createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; +``` + +The `compositeAlpha` field specifies if the alpha channel should be used for +blending with other windows in the window system. You'll almost always want to +simply ignore the alpha channel, hence `VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR`. + +```c++ +createInfo.presentMode = presentMode; +createInfo.clipped = VK_TRUE; +``` + +The `presentMode` member speaks for itself. If the `clipped` member is set to +`VK_TRUE` then that means that we don't care about the color of pixels that are +obscured, for example because another window is in front of them. Unless you +really need to be able to read these pixels back and get predictable results, +you'll get the best performance by enabling clipping. + +```c++ +createInfo.oldSwapchain = VK_NULL_HANDLE; +``` + +That leaves one last field, `oldSwapchain`. With Vulkan it's possible that your swap chain becomes invalid or unoptimized while your application is +running, for example because the window was resized. In that case the swap chain +actually needs to be recreated from scratch and a reference to the old one must +be specified in this field. This is a complex topic that we'll learn more about +in [a future chapter](!en/Drawing_a_triangle/Swap_chain_recreation). For now we'll +assume that we'll only ever create one swap chain. + +Now add a class member to store the `VkSwapchainKHR` object: + +```c++ +VkSwapchainKHR swapChain; +``` + +Creating the swap chain is now as simple as calling `vkCreateSwapchainKHR`: + +```c++ +if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); +} +``` + +The parameters are the logical device, swap chain creation info, optional custom +allocators and a pointer to the variable to store the handle in. No surprises +there. It should be cleaned up using `vkDestroySwapchainKHR` before the device: + +```c++ +void cleanup() { + vkDestroySwapchainKHR(device, swapChain, nullptr); + ... +} +``` + +Now run the application to ensure that the swap chain is created successfully! If at this point you get an access violation error in `vkCreateSwapchainKHR` or see a message like `Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll`, then see the [FAQ entry](!en/FAQ) about the Steam overlay layer. + +Try removing the `createInfo.imageExtent = extent;` line with validation layers +enabled. You'll see that one of the validation layers immediately catches the +mistake and a helpful message is printed: + +![](/images/swap_chain_validation_layer.png) + +## Retrieving the swap chain images + +The swap chain has been created now, so all that remains is retrieving the +handles of the `VkImage`s in it. We'll reference these during rendering +operations in later chapters. Add a class member to store the handles: + +```c++ +std::vector swapChainImages; +``` + +The images were created by the implementation for the swap chain and they will +be automatically cleaned up once the swap chain has been destroyed, therefore we +don't need to add any cleanup code. + +I'm adding the code to retrieve the handles to the end of the `createSwapChain` +function, right after the `vkCreateSwapchainKHR` call. Retrieving them is very +similar to the other times where we retrieved an array of objects from Vulkan. Remember that we only specified a minimum number of images in the swap chain, so the implementation is allowed to create a swap chain with more. That's why we'll first query the final number of images with `vkGetSwapchainImagesKHR`, then resize the container and finally call it again +to retrieve the handles. + +```c++ +vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); +swapChainImages.resize(imageCount); +vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); +``` + +One last thing, store the format and extent we've chosen for the swap chain +images in member variables. We'll need them in future chapters. + +```c++ +VkSwapchainKHR swapChain; +std::vector swapChainImages; +VkFormat swapChainImageFormat; +VkExtent2D swapChainExtent; + +... + +swapChainImageFormat = surfaceFormat.format; +swapChainExtent = extent; +``` + +We now have a set of images that can be drawn onto and can be presented to the +window. The next chapter will begin to cover how we can set up the images as +render targets and then we start looking into the actual graphics pipeline and +drawing commands! + +[C++ code](/code/06_swap_chain_creation.cpp) diff --git a/en/03_Drawing_a_triangle/01_Presentation/02_Image_views.md b/en/03_Drawing_a_triangle/01_Presentation/02_Image_views.md new file mode 100644 index 00000000..5988468a --- /dev/null +++ b/en/03_Drawing_a_triangle/01_Presentation/02_Image_views.md @@ -0,0 +1,127 @@ +To use any `VkImage`, including those in the swap chain, in the render pipeline +we have to create a `VkImageView` object. An image view is quite literally a +view into an image. It describes how to access the image and which part of the +image to access, for example if it should be treated as a 2D texture depth +texture without any mipmapping levels. + +In this chapter we'll write a `createImageViews` function that creates a basic +image view for every image in the swap chain so that we can use them as color +targets later on. + +First add a class member to store the image views in: + +```c++ +std::vector swapChainImageViews; +``` + +Create the `createImageViews` function and call it right after swap chain +creation. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); +} + +void createImageViews() { + +} +``` + +The first thing we need to do is resize the list to fit all of the image views +we'll be creating: + +```c++ +void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + +} +``` + +Next, set up the loop that iterates over all of the swap chain images. + +```c++ +for (size_t i = 0; i < swapChainImages.size(); i++) { + +} +``` + +The parameters for image view creation are specified in a +`VkImageViewCreateInfo` structure. The first few parameters are straightforward. + +```c++ +VkImageViewCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; +createInfo.image = swapChainImages[i]; +``` + +The `viewType` and `format` fields specify how the image data should be +interpreted. The `viewType` parameter allows you to treat images as 1D textures, +2D textures, 3D textures and cube maps. + +```c++ +createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; +createInfo.format = swapChainImageFormat; +``` + +The `components` field allows you to swizzle the color channels around. For +example, you can map all of the channels to the red channel for a monochrome +texture. You can also map constant values of `0` and `1` to a channel. In our +case we'll stick to the default mapping. + +```c++ +createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; +createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; +createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; +createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; +``` + +The `subresourceRange` field describes what the image's purpose is and which +part of the image should be accessed. Our images will be used as color targets +without any mipmapping levels or multiple layers. + +```c++ +createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +createInfo.subresourceRange.baseMipLevel = 0; +createInfo.subresourceRange.levelCount = 1; +createInfo.subresourceRange.baseArrayLayer = 0; +createInfo.subresourceRange.layerCount = 1; +``` + +If you were working on a stereographic 3D application, then you would create a +swap chain with multiple layers. You could then create multiple image views for +each image representing the views for the left and right eyes by accessing +different layers. + +Creating the image view is now a matter of calling `vkCreateImageView`: + +```c++ +if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); +} +``` + +Unlike images, the image views were explicitly created by us, so we need to add +a similar loop to destroy them again at the end of the program: + +```c++ +void cleanup() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + ... +} +``` + +An image view is sufficient to start using an image as a texture, but it's not +quite ready to be used as a render target just yet. That requires one more step +of indirection, known as a framebuffer. But first we'll have to set up the +graphics pipeline. + +[C++ code](/code/07_image_views.cpp) diff --git a/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md new file mode 100644 index 00000000..9ee7f739 --- /dev/null +++ b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md @@ -0,0 +1,99 @@ +Over the course of the next few chapters we'll be setting up a graphics pipeline +that is configured to draw our first triangle. The graphics pipeline is the +sequence of operations that take the vertices and textures of your meshes all +the way to the pixels in the render targets. A simplified overview is displayed +below: + +![](/images/vulkan_simplified_pipeline.svg) + +The *input assembler* collects the raw vertex data from the buffers you specify +and may also use an index buffer to repeat certain elements without having to +duplicate the vertex data itself. + +The *vertex shader* is run for every vertex and generally applies +transformations to turn vertex positions from model space to screen space. It +also passes per-vertex data down the pipeline. + +The *tessellation shaders* allow you to subdivide geometry based on certain +rules to increase the mesh quality. This is often used to make surfaces like +brick walls and staircases look less flat when they are nearby. + +The *geometry shader* is run on every primitive (triangle, line, point) and can +discard it or output more primitives than came in. This is similar to the +tessellation shader, but much more flexible. However, it is not used much in +today's applications because the performance is not that good on most graphics +cards except for Intel's integrated GPUs. + +The *rasterization* stage discretizes the primitives into *fragments*. These are +the pixel elements that they fill on the framebuffer. Any fragments that fall +outside the screen are discarded and the attributes outputted by the vertex +shader are interpolated across the fragments, as shown in the figure. Usually +the fragments that are behind other primitive fragments are also discarded here +because of depth testing. + +The *fragment shader* is invoked for every fragment that survives and determines +which framebuffer(s) the fragments are written to and with which color and depth +values. It can do this using the interpolated data from the vertex shader, which +can include things like texture coordinates and normals for lighting. + +The *color blending* stage applies operations to mix different fragments that +map to the same pixel in the framebuffer. Fragments can simply overwrite each +other, add up or be mixed based upon transparency. + +Stages with a green color are known as *fixed-function* stages. These stages +allow you to tweak their operations using parameters, but the way they work is +predefined. + +Stages with an orange color on the other hand are `programmable`, which means +that you can upload your own code to the graphics card to apply exactly the +operations you want. This allows you to use fragment shaders, for example, to +implement anything from texturing and lighting to ray tracers. These programs +run on many GPU cores simultaneously to process many objects, like vertices and +fragments in parallel. + +If you've used older APIs like OpenGL and Direct3D before, then you'll be used +to being able to change any pipeline settings at will with calls like +`glBlendFunc` and `OMSetBlendState`. The graphics pipeline in Vulkan is almost +completely immutable, so you must recreate the pipeline from scratch if you want +to change shaders, bind different framebuffers or change the blend function. The +disadvantage is that you'll have to create a number of pipelines that represent +all of the different combinations of states you want to use in your rendering +operations. However, because all of the operations you'll be doing in the +pipeline are known in advance, the driver can optimize for it much better. + +Some of the programmable stages are optional based on what you intend to do. For +example, the tessellation and geometry stages can be disabled if you are just +drawing simple geometry. If you are only interested in depth values then you can +disable the fragment shader stage, which is useful for [shadow map](https://en.wikipedia.org/wiki/Shadow_mapping) +generation. + +In the next chapter we'll first create the two programmable stages required to +put a triangle onto the screen: the vertex shader and fragment shader. The +fixed-function configuration like blending mode, viewport, rasterization will be +set up in the chapter after that. The final part of setting up the graphics +pipeline in Vulkan involves the specification of input and output framebuffers. + +Create a `createGraphicsPipeline` function that is called right after +`createImageViews` in `initVulkan`. We'll work on this function throughout the +following chapters. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); +} + +... + +void createGraphicsPipeline() { + +} +``` + +[C++ code](/code/08_graphics_pipeline.cpp) diff --git a/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md new file mode 100644 index 00000000..ef12e836 --- /dev/null +++ b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md @@ -0,0 +1,467 @@ +Unlike earlier APIs, shader code in Vulkan has to be specified in a bytecode +format as opposed to human-readable syntax like [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) +and [HLSL](https://en.wikipedia.org/wiki/High-Level_Shading_Language). This +bytecode format is called [SPIR-V](https://www.khronos.org/spir) and is designed +to be used with both Vulkan and OpenCL (both Khronos APIs). It is a format that +can be used to write graphics and compute shaders, but we will focus on shaders +used in Vulkan's graphics pipelines in this tutorial. + +The advantage of using a bytecode format is that the compilers written by GPU +vendors to turn shader code into native code are significantly less complex. The +past has shown that with human-readable syntax like GLSL, some GPU vendors were +rather flexible with their interpretation of the standard. If you happen to +write non-trivial shaders with a GPU from one of these vendors, then you'd risk +other vendor's drivers rejecting your code due to syntax errors, or worse, your +shader running differently because of compiler bugs. With a straightforward +bytecode format like SPIR-V that will hopefully be avoided. + +However, that does not mean that we need to write this bytecode by hand. Khronos +has released their own vendor-independent compiler that compiles GLSL to SPIR-V. +This compiler is designed to verify that your shader code is fully standards +compliant and produces one SPIR-V binary that you can ship with your program. +You can also include this compiler as a library to produce SPIR-V at runtime, +but we won't be doing that in this tutorial. Although we can use this compiler directly via `glslangValidator.exe`, we will be using `glslc.exe` by Google instead. The advantage of `glslc` is that it uses the same parameter format as well-known compilers like GCC and Clang and includes some extra functionality like *includes*. Both of them are already included in the Vulkan SDK, so you don't need to download anything extra. + +GLSL is a shading language with a C-style syntax. Programs written in it have a +`main` function that is invoked for every object. Instead of using parameters +for input and a return value as output, GLSL uses global variables to handle +input and output. The language includes many features to aid in graphics +programming, like built-in vector and matrix primitives. Functions for +operations like cross products, matrix-vector products and reflections around a +vector are included. The vector type is called `vec` with a number indicating +the amount of elements. For example, a 3D position would be stored in a `vec3`. +It is possible to access single components through members like `.x`, but it's +also possible to create a new vector from multiple components at the same time. +For example, the expression `vec3(1.0, 2.0, 3.0).xy` would result in `vec2`. The +constructors of vectors can also take combinations of vector objects and scalar +values. For example, a `vec3` can be constructed with +`vec3(vec2(1.0, 2.0), 3.0)`. + +As the previous chapter mentioned, we need to write a vertex shader and a +fragment shader to get a triangle on the screen. The next two sections will +cover the GLSL code of each of those and after that I'll show you how to produce +two SPIR-V binaries and load them into the program. + +## Vertex shader + +The vertex shader processes each incoming vertex. It takes its attributes, like +model space position, color, normal and texture coordinates as input. The output is +the final position in clip coordinates and the attributes that need to be passed +on to the fragment shader, like color and texture coordinates. These values will +then be interpolated over the fragments by the rasterizer to produce a smooth +gradient. + +A *clip coordinate* is a four dimensional vector from the vertex shader that is +subsequently turned into a *normalized device coordinate* by dividing the whole +vector by its last component. These normalized device coordinates are +[homogeneous coordinates](https://en.wikipedia.org/wiki/Homogeneous_coordinates) +that map the framebuffer to a [-1, 1] by [-1, 1] coordinate system that looks +like the following: + +![](/images/normalized_device_coordinates.svg) + +You should already be familiar with these if you have dabbled in computer +graphics before. If you have used OpenGL before, then you'll notice that the +sign of the Y coordinates is now flipped. The Z coordinate now uses the same +range as it does in Direct3D, from 0 to 1. + +For our first triangle we won't be applying any transformations, we'll just +specify the positions of the three vertices directly as normalized device +coordinates to create the following shape: + +![](/images/triangle_coordinates.svg) + +We can directly output normalized device coordinates by outputting them as clip +coordinates from the vertex shader with the last component set to `1`. That way +the division to transform clip coordinates to normalized device coordinates will +not change anything. + +Normally these coordinates would be stored in a vertex buffer, but creating a +vertex buffer in Vulkan and filling it with data is not trivial. Therefore I've +decided to postpone that until after we've had the satisfaction of seeing a +triangle pop up on the screen. We're going to do something a little unorthodox +in the meanwhile: include the coordinates directly inside the vertex shader. The +code looks like this: + +```glsl +#version 450 + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); +} +``` + +The `main` function is invoked for every vertex. The built-in `gl_VertexIndex` +variable contains the index of the current vertex. This is usually an index into +the vertex buffer, but in our case it will be an index into a hardcoded array +of vertex data. The position of each vertex is accessed from the constant array +in the shader and combined with dummy `z` and `w` components to produce a +position in clip coordinates. The built-in variable `gl_Position` functions as +the output. + +## Fragment shader + +The triangle that is formed by the positions from the vertex shader fills an +area on the screen with fragments. The fragment shader is invoked on these +fragments to produce a color and depth for the framebuffer (or framebuffers). A +simple fragment shader that outputs the color red for the entire triangle looks +like this: + +```glsl +#version 450 + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(1.0, 0.0, 0.0, 1.0); +} +``` + +The `main` function is called for every fragment just like the vertex shader +`main` function is called for every vertex. Colors in GLSL are 4-component +vectors with the R, G, B and alpha channels within the [0, 1] range. Unlike +`gl_Position` in the vertex shader, there is no built-in variable to output a +color for the current fragment. You have to specify your own output variable for +each framebuffer where the `layout(location = 0)` modifier specifies the index +of the framebuffer. The color red is written to this `outColor` variable that is +linked to the first (and only) framebuffer at index `0`. + +## Per-vertex colors + +Making the entire triangle red is not very interesting, wouldn't something like +the following look a lot nicer? + +![](/images/triangle_coordinates_colors.png) + +We have to make a couple of changes to both shaders to accomplish this. First +off, we need to specify a distinct color for each of the three vertices. The +vertex shader should now include an array with colors just like it does for +positions: + +```glsl +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); +``` + +Now we just need to pass these per-vertex colors to the fragment shader so it +can output their interpolated values to the framebuffer. Add an output for color +to the vertex shader and write to it in the `main` function: + +```glsl +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} +``` + +Next, we need to add a matching input in the fragment shader: + +```glsl +layout(location = 0) in vec3 fragColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} +``` + +The input variable does not necessarily have to use the same name, they will be +linked together using the indexes specified by the `location` directives. The +`main` function has been modified to output the color along with an alpha value. +As shown in the image above, the values for `fragColor` will be automatically +interpolated for the fragments between the three vertices, resulting in a smooth +gradient. + +## Compiling the shaders + +Create a directory called `shaders` in the root directory of your project and +store the vertex shader in a file called `shader.vert` and the fragment shader +in a file called `shader.frag` in that directory. GLSL shaders don't have an +official extension, but these two are commonly used to distinguish them. + +The contents of `shader.vert` should be: + +```glsl +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} +``` + +And the contents of `shader.frag` should be: + +```glsl +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} +``` + +We're now going to compile these into SPIR-V bytecode using the +`glslc` program. + +**Windows** + +Create a `compile.bat` file with the following contents: + +```bash +C:/VulkanSDK/x.x.x.x/Bin/glslc.exe shader.vert -o vert.spv +C:/VulkanSDK/x.x.x.x/Bin/glslc.exe shader.frag -o frag.spv +pause +``` + +Replace the path to `glslc.exe` with the path to where you installed +the Vulkan SDK. Double click the file to run it. + +**Linux** + +Create a `compile.sh` file with the following contents: + +```bash +/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.vert -o vert.spv +/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.frag -o frag.spv +``` + +Replace the path to `glslc` with the path to where you installed the +Vulkan SDK. Make the script executable with `chmod +x compile.sh` and run it. + +**End of platform-specific instructions** + +These two commands tell the compiler to read the GLSL source file and output a SPIR-V bytecode file using the `-o` (output) flag. + +If your shader contains a syntax error then the compiler will tell you the line +number and problem, as you would expect. Try leaving out a semicolon for example +and run the compile script again. Also try running the compiler without any +arguments to see what kinds of flags it supports. It can, for example, also +output the bytecode into a human-readable format so you can see exactly what +your shader is doing and any optimizations that have been applied at this stage. + +Compiling shaders on the commandline is one of the most straightforward options and it's the one that we'll use in this tutorial, but it's also possible to compile shaders directly from your own code. The Vulkan SDK includes [libshaderc](https://github.com/google/shaderc), which is a library to compile GLSL code to SPIR-V from within your program. + +## Loading a shader + +Now that we have a way of producing SPIR-V shaders, it's time to load them into +our program to plug them into the graphics pipeline at some point. We'll first +write a simple helper function to load the binary data from the files. + +```c++ +#include + +... + +static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } +} +``` + +The `readFile` function will read all of the bytes from the specified file and +return them in a byte array managed by `std::vector`. We start by opening the +file with two flags: + +* `ate`: Start reading at the end of the file +* `binary`: Read the file as binary file (avoid text transformations) + +The advantage of starting to read at the end of the file is that we can use the +read position to determine the size of the file and allocate a buffer: + +```c++ +size_t fileSize = (size_t) file.tellg(); +std::vector buffer(fileSize); +``` + +After that, we can seek back to the beginning of the file and read all of the +bytes at once: + +```c++ +file.seekg(0); +file.read(buffer.data(), fileSize); +``` + +And finally close the file and return the bytes: + +```c++ +file.close(); + +return buffer; +``` + +We'll now call this function from `createGraphicsPipeline` to load the bytecode +of the two shaders: + +```c++ +void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); +} +``` + +Make sure that the shaders are loaded correctly by printing the size of the +buffers and checking if they match the actual file size in bytes. Note that the code doesn't need to be null terminated since it's binary code and we will later be explicit about its size. + +## Creating shader modules + +Before we can pass the code to the pipeline, we have to wrap it in a +`VkShaderModule` object. Let's create a helper function `createShaderModule` to +do that. + +```c++ +VkShaderModule createShaderModule(const std::vector& code) { + +} +``` + +The function will take a buffer with the bytecode as parameter and create a +`VkShaderModule` from it. + +Creating a shader module is simple, we only need to specify a pointer to the +buffer with the bytecode and the length of it. This information is specified in +a `VkShaderModuleCreateInfo` structure. The one catch is that the size of the +bytecode is specified in bytes, but the bytecode pointer is a `uint32_t` pointer +rather than a `char` pointer. Therefore we will need to cast the pointer with +`reinterpret_cast` as shown below. When you perform a cast like this, you also +need to ensure that the data satisfies the alignment requirements of `uint32_t`. +Lucky for us, the data is stored in an `std::vector` where the default allocator +already ensures that the data satisfies the worst case alignment requirements. + +```c++ +VkShaderModuleCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; +createInfo.codeSize = code.size(); +createInfo.pCode = reinterpret_cast(code.data()); +``` + +The `VkShaderModule` can then be created with a call to `vkCreateShaderModule`: + +```c++ +VkShaderModule shaderModule; +if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); +} +``` + +The parameters are the same as those in previous object creation functions: the +logical device, pointer to create info structure, optional pointer to custom +allocators and handle output variable. The buffer with the code can be freed +immediately after creating the shader module. Don't forget to return the created +shader module: + +```c++ +return shaderModule; +``` + +Shader modules are just a thin wrapper around the shader bytecode that we've previously loaded from a file and the functions defined in it. The compilation and linking of the SPIR-V bytecode to machine code for execution by the GPU doesn't happen until the graphics pipeline is created. That means that we're allowed to destroy the shader modules again as soon as pipeline creation is finished, which is why we'll make them local variables in the `createGraphicsPipeline` function instead of class members: + +```c++ +void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); +``` + +The cleanup should then happen at the end of the function by adding two calls to `vkDestroyShaderModule`. All of the remaining code in this chapter will be inserted before these lines. + +```c++ + ... + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); +} +``` + +## Shader stage creation + +To actually use the shaders we'll need to assign them to a specific pipeline stage through `VkPipelineShaderStageCreateInfo` structures as part of the actual pipeline creation process. + +We'll start by filling in the structure for the vertex shader, again in the +`createGraphicsPipeline` function. + +```c++ +VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; +vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; +vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; +``` + +The first step, besides the obligatory `sType` member, is telling Vulkan in +which pipeline stage the shader is going to be used. There is an enum value for +each of the programmable stages described in the previous chapter. + +```c++ +vertShaderStageInfo.module = vertShaderModule; +vertShaderStageInfo.pName = "main"; +``` + +The next two members specify the shader module containing the code, and the +function to invoke, known as the *entrypoint*. That means that it's possible to combine multiple fragment +shaders into a single shader module and use different entry points to +differentiate between their behaviors. In this case we'll stick to the standard +`main`, however. + +There is one more (optional) member, `pSpecializationInfo`, which we won't be +using here, but is worth discussing. It allows you to specify values for shader +constants. You can use a single shader module where its behavior can be +configured at pipeline creation by specifying different values for the constants +used in it. This is more efficient than configuring the shader using variables +at render time, because the compiler can do optimizations like eliminating `if` +statements that depend on these values. If you don't have any constants like +that, then you can set the member to `nullptr`, which our struct initialization +does automatically. + +Modifying the structure to suit the fragment shader is easy: + +```c++ +VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; +fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; +fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; +fragShaderStageInfo.module = fragShaderModule; +fragShaderStageInfo.pName = "main"; +``` + +Finish by defining an array that contains these two structs, which we'll later +use to reference them in the actual pipeline creation step. + +```c++ +VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; +``` + +That's all there is to describing the programmable stages of the pipeline. In +the next chapter we'll look at the fixed-function stages. + +[C++ code](/code/09_shader_modules.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md new file mode 100644 index 00000000..5b4bfdec --- /dev/null +++ b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md @@ -0,0 +1,439 @@ + +The older graphics APIs provided default state for most of the stages of the +graphics pipeline. In Vulkan you have to be explicit about most pipeline states as +it'll be baked into an immutable pipeline state object. In this chapter we'll fill +in all of the structures to configure these fixed-function operations. + +## Dynamic state + +While *most* of the pipeline state needs to be baked into the pipeline state, +a limited amount of the state *can* actually be changed without recreating the +pipeline at draw time. Examples are the size of the viewport, line width +and blend constants. If you want to use dynamic state and keep these properties out, +then you'll have to fill in a `VkPipelineDynamicStateCreateInfo` structure like this: + +```c++ +std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR +}; + +VkPipelineDynamicStateCreateInfo dynamicState{}; +dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; +dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); +dynamicState.pDynamicStates = dynamicStates.data(); +``` + +This will cause the configuration of these values to be ignored and you will be +able (and required) to specify the data at drawing time. This results in a more flexible +setup and is very common for things like viewport and scissor state, which would +result in a more complex setup when being baked into the pipeline state. + +## Vertex input + +The `VkPipelineVertexInputStateCreateInfo` structure describes the format of the +vertex data that will be passed to the vertex shader. It describes this in +roughly two ways: + +* Bindings: spacing between data and whether the data is per-vertex or +per-instance (see [instancing](https://en.wikipedia.org/wiki/Geometry_instancing)) +* Attribute descriptions: type of the attributes passed to the vertex shader, +which binding to load them from and at which offset + +Because we're hard coding the vertex data directly in the vertex shader, we'll +fill in this structure to specify that there is no vertex data to load for now. +We'll get back to it in the vertex buffer chapter. + +```c++ +VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; +vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; +vertexInputInfo.vertexBindingDescriptionCount = 0; +vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optional +vertexInputInfo.vertexAttributeDescriptionCount = 0; +vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional +``` + +The `pVertexBindingDescriptions` and `pVertexAttributeDescriptions` members +point to an array of structs that describe the aforementioned details for +loading vertex data. Add this structure to the `createGraphicsPipeline` function +right after the `shaderStages` array. + +## Input assembly + +The `VkPipelineInputAssemblyStateCreateInfo` struct describes two things: what +kind of geometry will be drawn from the vertices and if primitive restart should +be enabled. The former is specified in the `topology` member and can have values +like: + +* `VK_PRIMITIVE_TOPOLOGY_POINT_LIST`: points from vertices +* `VK_PRIMITIVE_TOPOLOGY_LINE_LIST`: line from every 2 vertices without reuse +* `VK_PRIMITIVE_TOPOLOGY_LINE_STRIP`: the end vertex of every line is used as +start vertex for the next line +* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST`: triangle from every 3 vertices without +reuse +* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP `: the second and third vertex of every +triangle are used as first two vertices of the next triangle + +Normally, the vertices are loaded from the vertex buffer by index in sequential +order, but with an *element buffer* you can specify the indices to use yourself. +This allows you to perform optimizations like reusing vertices. If you set the +`primitiveRestartEnable` member to `VK_TRUE`, then it's possible to break up +lines and triangles in the `_STRIP` topology modes by using a special index of +`0xFFFF` or `0xFFFFFFFF`. + +We intend to draw triangles throughout this tutorial, so we'll stick to the +following data for the structure: + +```c++ +VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; +inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; +inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; +inputAssembly.primitiveRestartEnable = VK_FALSE; +``` + +## Viewports and scissors + +A viewport basically describes the region of the framebuffer that the output +will be rendered to. This will almost always be `(0, 0)` to `(width, height)` +and in this tutorial that will also be the case. + +```c++ +VkViewport viewport{}; +viewport.x = 0.0f; +viewport.y = 0.0f; +viewport.width = (float) swapChainExtent.width; +viewport.height = (float) swapChainExtent.height; +viewport.minDepth = 0.0f; +viewport.maxDepth = 1.0f; +``` + +Remember that the size of the swap chain and its images may differ from the +`WIDTH` and `HEIGHT` of the window. The swap chain images will be used as +framebuffers later on, so we should stick to their size. + +The `minDepth` and `maxDepth` values specify the range of depth values to use +for the framebuffer. These values must be within the `[0.0f, 1.0f]` range, but +`minDepth` may be higher than `maxDepth`. If you aren't doing anything special, +then you should stick to the standard values of `0.0f` and `1.0f`. + +While viewports define the transformation from the image to the framebuffer, +scissor rectangles define in which regions pixels will actually be stored. Any +pixels outside the scissor rectangles will be discarded by the rasterizer. They +function like a filter rather than a transformation. The difference is +illustrated below. Note that the left scissor rectangle is just one of the many +possibilities that would result in that image, as long as it's larger than the +viewport. + +![](/images/viewports_scissors.png) + +So if we wanted to draw to the entire framebuffer, we would specify a scissor rectangle that covers it entirely: + +```c++ +VkRect2D scissor{}; +scissor.offset = {0, 0}; +scissor.extent = swapChainExtent; +``` + +Viewport(s) and scissor rectangle(s) can either be specified as a static part of the pipeline or as a [dynamic state](#dynamic-state) set in the command buffer. While the former is more in line with the other states it's often convenient to make viewport and scissor state dynamic as it gives you a lot more flexibility. This is very common and all implementations can handle this dynamic state without a performance penalty. + +When opting for dynamic viewport(s) and scissor rectangle(s) you need to enable the respective dynamic states for the pipeline: + +```c++ +std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR +}; + +VkPipelineDynamicStateCreateInfo dynamicState{}; +dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; +dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); +dynamicState.pDynamicStates = dynamicStates.data(); +``` + +And then you only need to specify their count at pipeline creation time: + +```c++ +VkPipelineViewportStateCreateInfo viewportState{}; +viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; +viewportState.viewportCount = 1; +viewportState.scissorCount = 1; +``` + +The actual viewport(s) and scissor rectangle(s) will then later be set up at drawing time. + +With dynamic state it's even possible to specify different viewports and or scissor rectangles within a single command buffer. + +Without dynamic state, the viewport and scissor rectangle need to be set in the pipeline using the `VkPipelineViewportStateCreateInfo` struct. This makes the viewport and scissor rectangle for this pipeline immutable. +Any changes required to these values would require a new pipeline to be created with the new values. + +```c++ +VkPipelineViewportStateCreateInfo viewportState{}; +viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; +viewportState.viewportCount = 1; +viewportState.pViewports = &viewport; +viewportState.scissorCount = 1; +viewportState.pScissors = &scissor; +``` + +Independent of how you set them, it's possible to use multiple viewports and scissor rectangles on some graphics cards, so the structure members reference an array of them. Using multiple requires enabling a GPU feature (see logical device creation). + +## Rasterizer + +The rasterizer takes the geometry that is shaped by the vertices from the vertex +shader and turns it into fragments to be colored by the fragment shader. It also +performs [depth testing](https://en.wikipedia.org/wiki/Z-buffering), +[face culling](https://en.wikipedia.org/wiki/Back-face_culling) and the scissor +test, and it can be configured to output fragments that fill entire polygons or +just the edges (wireframe rendering). All this is configured using the +`VkPipelineRasterizationStateCreateInfo` structure. + +```c++ +VkPipelineRasterizationStateCreateInfo rasterizer{}; +rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; +rasterizer.depthClampEnable = VK_FALSE; +``` + +If `depthClampEnable` is set to `VK_TRUE`, then fragments that are beyond the +near and far planes are clamped to them as opposed to discarding them. This is +useful in some special cases like shadow maps. Using this requires enabling a +GPU feature. + +```c++ +rasterizer.rasterizerDiscardEnable = VK_FALSE; +``` + +If `rasterizerDiscardEnable` is set to `VK_TRUE`, then geometry never passes +through the rasterizer stage. This basically disables any output to the +framebuffer. + +```c++ +rasterizer.polygonMode = VK_POLYGON_MODE_FILL; +``` + +The `polygonMode` determines how fragments are generated for geometry. The +following modes are available: + +* `VK_POLYGON_MODE_FILL`: fill the area of the polygon with fragments +* `VK_POLYGON_MODE_LINE`: polygon edges are drawn as lines +* `VK_POLYGON_MODE_POINT`: polygon vertices are drawn as points + +Using any mode other than fill requires enabling a GPU feature. + +```c++ +rasterizer.lineWidth = 1.0f; +``` + +The `lineWidth` member is straightforward, it describes the thickness of lines +in terms of number of fragments. The maximum line width that is supported +depends on the hardware and any line thicker than `1.0f` requires you to enable +the `wideLines` GPU feature. + +```c++ +rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; +rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; +``` + +The `cullMode` variable determines the type of face culling to use. You can +disable culling, cull the front faces, cull the back faces or both. The +`frontFace` variable specifies the vertex order for faces to be considered +front-facing and can be clockwise or counterclockwise. + +```c++ +rasterizer.depthBiasEnable = VK_FALSE; +rasterizer.depthBiasConstantFactor = 0.0f; // Optional +rasterizer.depthBiasClamp = 0.0f; // Optional +rasterizer.depthBiasSlopeFactor = 0.0f; // Optional +``` + +The rasterizer can alter the depth values by adding a constant value or biasing +them based on a fragment's slope. This is sometimes used for shadow mapping, but +we won't be using it. Just set `depthBiasEnable` to `VK_FALSE`. + +## Multisampling + +The `VkPipelineMultisampleStateCreateInfo` struct configures multisampling, +which is one of the ways to perform [anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing). +It works by combining the fragment shader results of multiple polygons that +rasterize to the same pixel. This mainly occurs along edges, which is also where +the most noticeable aliasing artifacts occur. Because it doesn't need to run the +fragment shader multiple times if only one polygon maps to a pixel, it is +significantly less expensive than simply rendering to a higher resolution and +then downscaling. Enabling it requires enabling a GPU feature. + +```c++ +VkPipelineMultisampleStateCreateInfo multisampling{}; +multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; +multisampling.sampleShadingEnable = VK_FALSE; +multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; +multisampling.minSampleShading = 1.0f; // Optional +multisampling.pSampleMask = nullptr; // Optional +multisampling.alphaToCoverageEnable = VK_FALSE; // Optional +multisampling.alphaToOneEnable = VK_FALSE; // Optional +``` + +We'll revisit multisampling in later chapter, for now let's keep it disabled. + +## Depth and stencil testing + +If you are using a depth and/or stencil buffer, then you also need to configure +the depth and stencil tests using `VkPipelineDepthStencilStateCreateInfo`. We +don't have one right now, so we can simply pass a `nullptr` instead of a pointer +to such a struct. We'll get back to it in the depth buffering chapter. + +## Color blending + +After a fragment shader has returned a color, it needs to be combined with the +color that is already in the framebuffer. This transformation is known as color +blending and there are two ways to do it: + +* Mix the old and new value to produce a final color +* Combine the old and new value using a bitwise operation + +There are two types of structs to configure color blending. The first struct, +`VkPipelineColorBlendAttachmentState` contains the configuration per attached +framebuffer and the second struct, `VkPipelineColorBlendStateCreateInfo` +contains the *global* color blending settings. In our case we only have one +framebuffer: + +```c++ +VkPipelineColorBlendAttachmentState colorBlendAttachment{}; +colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; +colorBlendAttachment.blendEnable = VK_FALSE; +colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional +colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional +colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional +colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional +colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional +colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional +``` + +This per-framebuffer struct allows you to configure the first way of color +blending. The operations that will be performed are best demonstrated using the +following pseudocode: + +```c++ +if (blendEnable) { + finalColor.rgb = (srcColorBlendFactor * newColor.rgb) (dstColorBlendFactor * oldColor.rgb); + finalColor.a = (srcAlphaBlendFactor * newColor.a) (dstAlphaBlendFactor * oldColor.a); +} else { + finalColor = newColor; +} + +finalColor = finalColor & colorWriteMask; +``` + +If `blendEnable` is set to `VK_FALSE`, then the new color from the fragment +shader is passed through unmodified. Otherwise, the two mixing operations are +performed to compute a new color. The resulting color is AND'd with the +`colorWriteMask` to determine which channels are actually passed through. + +The most common way to use color blending is to implement alpha blending, where +we want the new color to be blended with the old color based on its opacity. The +`finalColor` should then be computed as follows: + +```c++ +finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor; +finalColor.a = newAlpha.a; +``` + +This can be accomplished with the following parameters: + +```c++ +colorBlendAttachment.blendEnable = VK_TRUE; +colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; +colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; +colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; +colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; +colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; +colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; +``` + +You can find all of the possible operations in the `VkBlendFactor` and +`VkBlendOp` enumerations in the specification. + +The second structure references the array of structures for all of the +framebuffers and allows you to set blend constants that you can use as blend +factors in the aforementioned calculations. + +```c++ +VkPipelineColorBlendStateCreateInfo colorBlending{}; +colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; +colorBlending.logicOpEnable = VK_FALSE; +colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional +colorBlending.attachmentCount = 1; +colorBlending.pAttachments = &colorBlendAttachment; +colorBlending.blendConstants[0] = 0.0f; // Optional +colorBlending.blendConstants[1] = 0.0f; // Optional +colorBlending.blendConstants[2] = 0.0f; // Optional +colorBlending.blendConstants[3] = 0.0f; // Optional +``` + +If you want to use the second method of blending (bitwise combination), then you +should set `logicOpEnable` to `VK_TRUE`. The bitwise operation can then be +specified in the `logicOp` field. Note that this will automatically disable the +first method, as if you had set `blendEnable` to `VK_FALSE` for every +attached framebuffer! The `colorWriteMask` will also be used in this mode to +determine which channels in the framebuffer will actually be affected. It is +also possible to disable both modes, as we've done here, in which case the +fragment colors will be written to the framebuffer unmodified. + +## Pipeline layout + +You can use `uniform` values in shaders, which are globals similar to dynamic +state variables that can be changed at drawing time to alter the behavior of +your shaders without having to recreate them. They are commonly used to pass the +transformation matrix to the vertex shader, or to create texture samplers in the +fragment shader. + +These uniform values need to be specified during pipeline creation by creating a +`VkPipelineLayout` object. Even though we won't be using them until a future +chapter, we are still required to create an empty pipeline layout. + +Create a class member to hold this object, because we'll refer to it from other +functions at a later point in time: + +```c++ +VkPipelineLayout pipelineLayout; +``` + +And then create the object in the `createGraphicsPipeline` function: + +```c++ +VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; +pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +pipelineLayoutInfo.setLayoutCount = 0; // Optional +pipelineLayoutInfo.pSetLayouts = nullptr; // Optional +pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional +pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optional + +if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); +} +``` + +The structure also specifies *push constants*, which are another way of passing +dynamic values to shaders that we may get into in a future chapter. The pipeline +layout will be referenced throughout the program's lifetime, so it should be +destroyed at the end: + +```c++ +void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + ... +} +``` + +## Conclusion + +That's it for all of the fixed-function state! It's a lot of work to set all of +this up from scratch, but the advantage is that we're now nearly fully aware of +everything that is going on in the graphics pipeline! This reduces the chance of +running into unexpected behavior because the default state of certain components +is not what you expect. + +There is however one more object to create before we can finally create the +graphics pipeline and that is a [render pass](!en/Drawing_a_triangle/Graphics_pipeline_basics/Render_passes). + +[C++ code](/code/10_fixed_functions.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md new file mode 100644 index 00000000..a635d32f --- /dev/null +++ b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md @@ -0,0 +1,215 @@ +## Setup + +Before we can finish creating the pipeline, we need to tell Vulkan about the +framebuffer attachments that will be used while rendering. We need to specify +how many color and depth buffers there will be, how many samples to use for each +of them and how their contents should be handled throughout the rendering +operations. All of this information is wrapped in a *render pass* object, for +which we'll create a new `createRenderPass` function. Call this function from +`initVulkan` before `createGraphicsPipeline`. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); +} + +... + +void createRenderPass() { + +} +``` + +## Attachment description + +In our case we'll have just a single color buffer attachment represented by one +of the images from the swap chain. + +```c++ +void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; +} +``` + +The `format` of the color attachment should match the format of the swap chain +images, and we're not doing anything with multisampling yet, so we'll stick to 1 +sample. + +```c++ +colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; +colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; +``` + +The `loadOp` and `storeOp` determine what to do with the data in the attachment +before rendering and after rendering. We have the following choices for +`loadOp`: + +* `VK_ATTACHMENT_LOAD_OP_LOAD`: Preserve the existing contents of the attachment +* `VK_ATTACHMENT_LOAD_OP_CLEAR`: Clear the values to a constant at the start +* `VK_ATTACHMENT_LOAD_OP_DONT_CARE`: Existing contents are undefined; we don't +care about them + +In our case we're going to use the clear operation to clear the framebuffer to +black before drawing a new frame. There are only two possibilities for the +`storeOp`: + +* `VK_ATTACHMENT_STORE_OP_STORE`: Rendered contents will be stored in memory and +can be read later +* `VK_ATTACHMENT_STORE_OP_DONT_CARE`: Contents of the framebuffer will be +undefined after the rendering operation + +We're interested in seeing the rendered triangle on the screen, so we're going +with the store operation here. + +```c++ +colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; +colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; +``` + +The `loadOp` and `storeOp` apply to color and depth data, and `stencilLoadOp` / +`stencilStoreOp` apply to stencil data. Our application won't do anything with +the stencil buffer, so the results of loading and storing are irrelevant. + +```c++ +colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; +``` + +Textures and framebuffers in Vulkan are represented by `VkImage` objects with a +certain pixel format, however the layout of the pixels in memory can change +based on what you're trying to do with an image. + +Some of the most common layouts are: + +* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`: Images used as color attachment +* `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`: Images to be presented in the swap chain +* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`: Images to be used as destination for a +memory copy operation + +We'll discuss this topic in more depth in the texturing chapter, but what's +important to know right now is that images need to be transitioned to specific +layouts that are suitable for the operation that they're going to be involved in +next. + +The `initialLayout` specifies which layout the image will have before the render +pass begins. The `finalLayout` specifies the layout to automatically transition +to when the render pass finishes. Using `VK_IMAGE_LAYOUT_UNDEFINED` for +`initialLayout` means that we don't care what previous layout the image was in. +The caveat of this special value is that the contents of the image are not +guaranteed to be preserved, but that doesn't matter since we're going to clear +it anyway. We want the image to be ready for presentation using the swap chain +after rendering, which is why we use `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` as +`finalLayout`. + +## Subpasses and attachment references + +A single render pass can consist of multiple subpasses. Subpasses are subsequent +rendering operations that depend on the contents of framebuffers in previous +passes, for example a sequence of post-processing effects that are applied one +after another. If you group these rendering operations into one render pass, +then Vulkan is able to reorder the operations and conserve memory bandwidth for +possibly better performance. For our very first triangle, however, we'll stick +to a single subpass. + +Every subpass references one or more of the attachments that we've described +using the structure in the previous sections. These references are themselves +`VkAttachmentReference` structs that look like this: + +```c++ +VkAttachmentReference colorAttachmentRef{}; +colorAttachmentRef.attachment = 0; +colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; +``` + +The `attachment` parameter specifies which attachment to reference by its index +in the attachment descriptions array. Our array consists of a single +`VkAttachmentDescription`, so its index is `0`. The `layout` specifies which +layout we would like the attachment to have during a subpass that uses this +reference. Vulkan will automatically transition the attachment to this layout +when the subpass is started. We intend to use the attachment to function as a +color buffer and the `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` layout will give +us the best performance, as its name implies. + +The subpass is described using a `VkSubpassDescription` structure: + +```c++ +VkSubpassDescription subpass{}; +subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; +``` + +Vulkan may also support compute subpasses in the future, so we have to be +explicit about this being a graphics subpass. Next, we specify the reference to +the color attachment: + +```c++ +subpass.colorAttachmentCount = 1; +subpass.pColorAttachments = &colorAttachmentRef; +``` + +The index of the attachment in this array is directly referenced from the +fragment shader with the `layout(location = 0) out vec4 outColor` directive! + +The following other types of attachments can be referenced by a subpass: + +* `pInputAttachments`: Attachments that are read from a shader +* `pResolveAttachments`: Attachments used for multisampling color attachments +* `pDepthStencilAttachment`: Attachment for depth and stencil data +* `pPreserveAttachments`: Attachments that are not used by this subpass, but for +which the data must be preserved + +## Render pass + +Now that the attachment and a basic subpass referencing it have been described, +we can create the render pass itself. Create a new class member variable to hold +the `VkRenderPass` object right above the `pipelineLayout` variable: + +```c++ +VkRenderPass renderPass; +VkPipelineLayout pipelineLayout; +``` + +The render pass object can then be created by filling in the +`VkRenderPassCreateInfo` structure with an array of attachments and subpasses. +The `VkAttachmentReference` objects reference attachments using the indices of +this array. + +```c++ +VkRenderPassCreateInfo renderPassInfo{}; +renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; +renderPassInfo.attachmentCount = 1; +renderPassInfo.pAttachments = &colorAttachment; +renderPassInfo.subpassCount = 1; +renderPassInfo.pSubpasses = &subpass; + +if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); +} +``` + +Just like the pipeline layout, the render pass will be referenced throughout the +program, so it should only be cleaned up at the end: + +```c++ +void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + ... +} +``` + +That was a lot of work, but in the next chapter it all comes together to finally +create the graphics pipeline object! + +[C++ code](/code/11_render_passes.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md new file mode 100644 index 00000000..4a16585e --- /dev/null +++ b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md @@ -0,0 +1,122 @@ +We can now combine all of the structures and objects from the previous chapters +to create the graphics pipeline! Here's the types of objects we have now, as a +quick recap: + +* Shader stages: the shader modules that define the functionality of the +programmable stages of the graphics pipeline +* Fixed-function state: all of the structures that define the fixed-function +stages of the pipeline, like input assembly, rasterizer, viewport and color +blending +* Pipeline layout: the uniform and push values referenced by the shader that can +be updated at draw time +* Render pass: the attachments referenced by the pipeline stages and their usage + +All of these combined fully define the functionality of the graphics pipeline, +so we can now begin filling in the `VkGraphicsPipelineCreateInfo` structure at +the end of the `createGraphicsPipeline` function. But before the calls to +`vkDestroyShaderModule` because these are still to be used during the creation. + +```c++ +VkGraphicsPipelineCreateInfo pipelineInfo{}; +pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; +pipelineInfo.stageCount = 2; +pipelineInfo.pStages = shaderStages; +``` + +We start by referencing the array of `VkPipelineShaderStageCreateInfo` structs. + +```c++ +pipelineInfo.pVertexInputState = &vertexInputInfo; +pipelineInfo.pInputAssemblyState = &inputAssembly; +pipelineInfo.pViewportState = &viewportState; +pipelineInfo.pRasterizationState = &rasterizer; +pipelineInfo.pMultisampleState = &multisampling; +pipelineInfo.pDepthStencilState = nullptr; // Optional +pipelineInfo.pColorBlendState = &colorBlending; +pipelineInfo.pDynamicState = &dynamicState; +``` + +Then we reference all of the structures describing the fixed-function stage. + +```c++ +pipelineInfo.layout = pipelineLayout; +``` + +After that comes the pipeline layout, which is a Vulkan handle rather than a +struct pointer. + +```c++ +pipelineInfo.renderPass = renderPass; +pipelineInfo.subpass = 0; +``` + +And finally we have the reference to the render pass and the index of the sub +pass where this graphics pipeline will be used. It is also possible to use other +render passes with this pipeline instead of this specific instance, but they +have to be *compatible* with `renderPass`. The requirements for compatibility +are described [here](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility), +but we won't be using that feature in this tutorial. + +```c++ +pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional +pipelineInfo.basePipelineIndex = -1; // Optional +``` + +There are actually two more parameters: `basePipelineHandle` and +`basePipelineIndex`. Vulkan allows you to create a new graphics pipeline by +deriving from an existing pipeline. The idea of pipeline derivatives is that it +is less expensive to set up pipelines when they have much functionality in +common with an existing pipeline and switching between pipelines from the same +parent can also be done quicker. You can either specify the handle of an +existing pipeline with `basePipelineHandle` or reference another pipeline that +is about to be created by index with `basePipelineIndex`. Right now there is +only a single pipeline, so we'll simply specify a null handle and an invalid +index. These values are only used if the `VK_PIPELINE_CREATE_DERIVATIVE_BIT` +flag is also specified in the `flags` field of `VkGraphicsPipelineCreateInfo`. + +Now prepare for the final step by creating a class member to hold the +`VkPipeline` object: + +```c++ +VkPipeline graphicsPipeline; +``` + +And finally create the graphics pipeline: + +```c++ +if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); +} +``` + +The `vkCreateGraphicsPipelines` function actually has more parameters than the +usual object creation functions in Vulkan. It is designed to take multiple +`VkGraphicsPipelineCreateInfo` objects and create multiple `VkPipeline` objects +in a single call. + +The second parameter, for which we've passed the `VK_NULL_HANDLE` argument, +references an optional `VkPipelineCache` object. A pipeline cache can be used to +store and reuse data relevant to pipeline creation across multiple calls to +`vkCreateGraphicsPipelines` and even across program executions if the cache is +stored to a file. This makes it possible to significantly speed up pipeline +creation at a later time. We'll get into this in the pipeline cache chapter. + +The graphics pipeline is required for all common drawing operations, so it +should also only be destroyed at the end of the program: + +```c++ +void cleanup() { + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + ... +} +``` + +Now run your program to confirm that all this hard work has resulted in a +successful pipeline creation! We are already getting quite close to seeing +something pop up on the screen. In the next couple of chapters we'll set up the +actual framebuffers from the swap chain images and prepare the drawing commands. + +[C++ code](/code/12_graphics_pipeline_complete.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md b/en/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md new file mode 100644 index 00000000..bf7f84a7 --- /dev/null +++ b/en/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md @@ -0,0 +1,107 @@ +We've talked a lot about framebuffers in the past few chapters and we've set up +the render pass to expect a single framebuffer with the same format as the swap +chain images, but we haven't actually created any yet. + +The attachments specified during render pass creation are bound by wrapping them +into a `VkFramebuffer` object. A framebuffer object references all of the +`VkImageView` objects that represent the attachments. In our case that will be +only a single one: the color attachment. However, the image that we have to use +for the attachment depends on which image the swap chain returns when we retrieve one +for presentation. That means that we have to create a framebuffer for all of the +images in the swap chain and use the one that corresponds to the retrieved image +at drawing time. + +To that end, create another `std::vector` class member to hold the framebuffers: + +```c++ +std::vector swapChainFramebuffers; +``` + +We'll create the objects for this array in a new function `createFramebuffers` +that is called from `initVulkan` right after creating the graphics pipeline: + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); +} + +... + +void createFramebuffers() { + +} +``` + +Start by resizing the container to hold all of the framebuffers: + +```c++ +void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); +} +``` + +We'll then iterate through the image views and create framebuffers from them: + +```c++ +for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } +} +``` + +As you can see, creation of framebuffers is quite straightforward. We first need +to specify with which `renderPass` the framebuffer needs to be compatible. You +can only use a framebuffer with the render passes that it is compatible with, +which roughly means that they use the same number and type of attachments. + +The `attachmentCount` and `pAttachments` parameters specify the `VkImageView` +objects that should be bound to the respective attachment descriptions in +the render pass `pAttachment` array. + +The `width` and `height` parameters are self-explanatory and `layers` refers to +the number of layers in image arrays. Our swap chain images are single images, +so the number of layers is `1`. + +We should delete the framebuffers before the image views and render pass that +they are based on, but only after we've finished rendering: + +```c++ +void cleanup() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + ... +} +``` + +We've now reached the milestone where we have all of the objects that are +required for rendering. In the next chapter we're going to write the first +actual drawing commands. + +[C++ code](/code/13_framebuffers.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md b/en/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md new file mode 100644 index 00000000..61a40b4f --- /dev/null +++ b/en/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md @@ -0,0 +1,344 @@ +Commands in Vulkan, like drawing operations and memory transfers, are not +executed directly using function calls. You have to record all of the operations +you want to perform in command buffer objects. The advantage of this is that when +we are ready to tell the Vulkan what we want to do, all of the commands are +submitted together and Vulkan can more efficiently process the commands since all +of them are available together. In addition, this allows command recording to +happen in multiple threads if so desired. + +## Command pools + +We have to create a command pool before we can create command buffers. Command +pools manage the memory that is used to store the buffers and command buffers +are allocated from them. Add a new class member to store a `VkCommandPool`: + +```c++ +VkCommandPool commandPool; +``` + +Then create a new function `createCommandPool` and call it from `initVulkan` +after the framebuffers were created. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); +} + +... + +void createCommandPool() { + +} +``` + +Command pool creation only takes two parameters: + +```c++ +QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + +VkCommandPoolCreateInfo poolInfo{}; +poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; +poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; +poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); +``` + +There are two possible flags for command pools: + +* `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT`: Hint that command buffers are +rerecorded with new commands very often (may change memory allocation behavior) +* `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT`: Allow command buffers to be +rerecorded individually, without this flag they all have to be reset together + +We will be recording a command buffer every frame, so we want to be able to +reset and rerecord over it. Thus, we need to set the +`VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT` flag bit for our command pool. + +Command buffers are executed by submitting them on one of the device queues, +like the graphics and presentation queues we retrieved. Each command pool can +only allocate command buffers that are submitted on a single type of queue. +We're going to record commands for drawing, which is why we've chosen the +graphics queue family. + + +```c++ +if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); +} +``` + +Finish creating the command pool using the `vkCreateCommandPool` function. It +doesn't have any special parameters. Commands will be used throughout the +program to draw things on the screen, so the pool should only be destroyed at +the end: + +```c++ +void cleanup() { + vkDestroyCommandPool(device, commandPool, nullptr); + + ... +} +``` + +## Command buffer allocation + +We can now start allocating command buffers. + +Create a `VkCommandBuffer` object as a class member. Command buffers +will be automatically freed when their command pool is destroyed, so we don't +need explicit cleanup. + +```c++ +VkCommandBuffer commandBuffer; +``` + +We'll now start working on a `createCommandBuffer` function to allocate a single +command buffer from the command pool. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffer(); +} + +... + +void createCommandBuffer() { + +} +``` + +Command buffers are allocated with the `vkAllocateCommandBuffers` function, +which takes a `VkCommandBufferAllocateInfo` struct as parameter that specifies +the command pool and number of buffers to allocate: + +```c++ +VkCommandBufferAllocateInfo allocInfo{}; +allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; +allocInfo.commandPool = commandPool; +allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; +allocInfo.commandBufferCount = 1; + +if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); +} +``` + +The `level` parameter specifies if the allocated command buffers are primary or +secondary command buffers. + +* `VK_COMMAND_BUFFER_LEVEL_PRIMARY`: Can be submitted to a queue for execution, +but cannot be called from other command buffers. +* `VK_COMMAND_BUFFER_LEVEL_SECONDARY`: Cannot be submitted directly, but can be +called from primary command buffers. + +We won't make use of the secondary command buffer functionality here, but you +can imagine that it's helpful to reuse common operations from primary command +buffers. + +Since we are only allocating one command buffer, the `commandBufferCount` parameter +is just one. + +## Command buffer recording + +We'll now start working on the `recordCommandBuffer` function that writes the +commands we want to execute into a command buffer. The `VkCommandBuffer` used +will be passed in as a parameter, as well as the index of the current swapchain +image we want to write to. + +```c++ +void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + +} +``` + +We always begin recording a command buffer by calling `vkBeginCommandBuffer` +with a small `VkCommandBufferBeginInfo` structure as argument that specifies +some details about the usage of this specific command buffer. + +```c++ +VkCommandBufferBeginInfo beginInfo{}; +beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; +beginInfo.flags = 0; // Optional +beginInfo.pInheritanceInfo = nullptr; // Optional + +if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); +} +``` + +The `flags` parameter specifies how we're going to use the command buffer. The +following values are available: + +* `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`: The command buffer will be +rerecorded right after executing it once. +* `VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT`: This is a secondary +command buffer that will be entirely within a single render pass. +* `VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT`: The command buffer can be +resubmitted while it is also already pending execution. + +None of these flags are applicable for us right now. + +The `pInheritanceInfo` parameter is only relevant for secondary command buffers. +It specifies which state to inherit from the calling primary command buffers. + +If the command buffer was already recorded once, then a call to +`vkBeginCommandBuffer` will implicitly reset it. It's not possible to append +commands to a buffer at a later time. + +## Starting a render pass + +Drawing starts by beginning the render pass with `vkCmdBeginRenderPass`. The +render pass is configured using some parameters in a `VkRenderPassBeginInfo` +struct. + +```c++ +VkRenderPassBeginInfo renderPassInfo{}; +renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; +renderPassInfo.renderPass = renderPass; +renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; +``` + +The first parameters are the render pass itself and the attachments to bind. We +created a framebuffer for each swap chain image where it is specified as a color +attachment. Thus we need to bind the framebuffer for the swapchain image we want +to draw to. Using the imageIndex parameter which was passed in, we can pick the +right framebuffer for the current swapchain image. + +```c++ +renderPassInfo.renderArea.offset = {0, 0}; +renderPassInfo.renderArea.extent = swapChainExtent; +``` + +The next two parameters define the size of the render area. The render area +defines where shader loads and stores will take place. The pixels outside this +region will have undefined values. It should match the size of the attachments +for best performance. + +```c++ +VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; +renderPassInfo.clearValueCount = 1; +renderPassInfo.pClearValues = &clearColor; +``` + +The last two parameters define the clear values to use for +`VK_ATTACHMENT_LOAD_OP_CLEAR`, which we used as load operation for the color +attachment. I've defined the clear color to simply be black with 100% opacity. + +```c++ +vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); +``` + +The render pass can now begin. All of the functions that record commands can be +recognized by their `vkCmd` prefix. They all return `void`, so there will be no +error handling until we've finished recording. + +The first parameter for every command is always the command buffer to record the +command to. The second parameter specifies the details of the render pass we've +just provided. The final parameter controls how the drawing commands within the +render pass will be provided. It can have one of two values: + +* `VK_SUBPASS_CONTENTS_INLINE`: The render pass commands will be embedded in +the primary command buffer itself and no secondary command buffers will be +executed. +* `VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS`: The render pass commands will +be executed from secondary command buffers. + +We will not be using secondary command buffers, so we'll go with the first +option. + +## Basic drawing commands + +We can now bind the graphics pipeline: + +```c++ +vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); +``` + +The second parameter specifies if the pipeline object is a graphics or compute +pipeline. We've now told Vulkan which operations to execute in the graphics +pipeline and which attachment to use in the fragment shader. + +As noted in the [fixed functions chapter](../02_Graphics_pipeline_basics/02_Fixed_functions.md#dynamic-state), +we did specify viewport and scissor state for this pipeline to be dynamic. +So we need to set them in the command buffer before issuing our draw command: + +```c++ +VkViewport viewport{}; +viewport.x = 0.0f; +viewport.y = 0.0f; +viewport.width = static_cast(swapChainExtent.width); +viewport.height = static_cast(swapChainExtent.height); +viewport.minDepth = 0.0f; +viewport.maxDepth = 1.0f; +vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + +VkRect2D scissor{}; +scissor.offset = {0, 0}; +scissor.extent = swapChainExtent; +vkCmdSetScissor(commandBuffer, 0, 1, &scissor); +``` + +Now we are ready to issue the draw command for the triangle: + +```c++ +vkCmdDraw(commandBuffer, 3, 1, 0, 0); +``` + +The actual `vkCmdDraw` function is a bit anticlimactic, but it's so simple +because of all the information we specified in advance. It has the following +parameters, aside from the command buffer: + +* `vertexCount`: Even though we don't have a vertex buffer, we technically still +have 3 vertices to draw. +* `instanceCount`: Used for instanced rendering, use `1` if you're not doing +that. +* `firstVertex`: Used as an offset into the vertex buffer, defines the lowest +value of `gl_VertexIndex`. +* `firstInstance`: Used as an offset for instanced rendering, defines the lowest +value of `gl_InstanceIndex`. + +## Finishing up + +The render pass can now be ended: + +```c++ +vkCmdEndRenderPass(commandBuffer); +``` + +And we've finished recording the command buffer: + +```c++ +if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); +} +``` + + + +In the next chapter we'll write the code for the main loop, which will acquire +an image from the swap chain, record and execute a command buffer, then return the +finished image to the swap chain. + +[C++ code](/code/14_command_buffers.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md b/en/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md new file mode 100644 index 00000000..233c059d --- /dev/null +++ b/en/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md @@ -0,0 +1,577 @@ + +This is the chapter where everything is going to come together. We're going to +write the `drawFrame` function that will be called from the main loop to put the +triangle on the screen. Let's start by creating the function and call it from +`mainLoop`: + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } +} + +... + +void drawFrame() { + +} +``` + +## Outline of a frame + +At a high level, rendering a frame in Vulkan consists of a common set of steps: + +* Wait for the previous frame to finish +* Acquire an image from the swap chain +* Record a command buffer which draws the scene onto that image +* Submit the recorded command buffer +* Present the swap chain image + +While we will expand the drawing function in later chapters, for now this is the +core of our render loop. + + + +## Synchronization + + + +A core design philosophy in Vulkan is that synchronization of execution on +the GPU is explicit. The order of operations is up to us to define using various +synchronization primitives which tell the driver the order we want things to run +in. This means that many Vulkan API calls which start executing work on the GPU +are asynchronous, the functions will return before the operation has finished. + +In this chapter there are a number of events that we need to order explicitly +because they happen on the GPU, such as: + +* Acquire an image from the swap chain +* Execute commands that draw onto the acquired image +* Present that image to the screen for presentation, returning it to the swapchain + +Each of these events is set in motion using a single function call, but are all +executed asynchronously. The function calls will return before the operations +are actually finished and the order of execution is also undefined. That is +unfortunate, because each of the operations depends on the previous one +finishing. Thus we need to explore which primitives we can use to achieve +the desired ordering. + +### Semaphores + +A semaphore is used to add order between queue operations. Queue operations +refer to the work we submit to a queue, either in a command buffer or from +within a function as we will see later. Examples of queues are the graphics +queue and the presentation queue. Semaphores are used both to order work inside +the same queue and between different queues. + +There happens to be two kinds of semaphores in Vulkan, binary and timeline. +Because only binary semaphores will be used in this tutorial, we will not +discuss timeline semaphores. Further mention of the term semaphore exclusively +refers to binary semaphores. + +A semaphore is either unsignaled or signaled. It begins life as unsignaled. The +way we use a semaphore to order queue operations is by providing the same +semaphore as a 'signal' semaphore in one queue operation and as a 'wait' +semaphore in another queue operation. For example, lets say we have semaphore S +and queue operations A and B that we want to execute in order. What we tell +Vulkan is that operation A will 'signal' semaphore S when it finishes executing, +and operation B will 'wait' on semaphore S before it begins executing. When +operation A finishes, semaphore S will be signaled, while operation B wont +start until S is signaled. After operation B begins executing, semaphore S +is automatically reset back to being unsignaled, allowing it to be used again. + +Pseudo-code of what was just described: +``` +VkCommandBuffer A, B = ... // record command buffers +VkSemaphore S = ... // create a semaphore + +// enqueue A, signal S when done - starts executing immediately +vkQueueSubmit(work: A, signal: S, wait: None) + +// enqueue B, wait on S to start +vkQueueSubmit(work: B, signal: None, wait: S) +``` + +Note that in this code snippet, both calls to `vkQueueSubmit()` return +immediately - the waiting only happens on the GPU. The CPU continues running +without blocking. To make the CPU wait, we need a different synchronization +primitive, which we will now describe. + +### Fences + +A fence has a similar purpose, in that it is used to synchronize execution, but +it is for ordering the execution on the CPU, otherwise known as the host. +Simply put, if the host needs to know when the GPU has finished something, we +use a fence. + +Similar to semaphores, fences are either in a signaled or unsignaled state. +Whenever we submit work to execute, we can attach a fence to that work. When +the work is finished, the fence will be signaled. Then we can make the host +wait for the fence to be signaled, guaranteeing that the work has finished +before the host continues. + +A concrete example is taking a screenshot. Say we have already done the +necessary work on the GPU. Now need to transfer the image from the GPU over +to the host and then save the memory to a file. We have command buffer A which +executes the transfer and fence F. We submit command buffer A with fence F, +then immediately tell the host to wait for F to signal. This causes the host to +block until command buffer A finishes execution. Thus we are safe to let the +host save the file to disk, as the memory transfer has completed. + +Pseudo-code for what was described: +``` +VkCommandBuffer A = ... // record command buffer with the transfer +VkFence F = ... // create the fence + +// enqueue A, start work immediately, signal F when done +vkQueueSubmit(work: A, fence: F) + +vkWaitForFence(F) // blocks execution until A has finished executing + +save_screenshot_to_disk() // can't run until the transfer has finished +``` + +Unlike the semaphore example, this example *does* block host execution. This +means the host won't do anything except wait until execution has finished. For +this case, we had to make sure the transfer was complete before we could save +the screenshot to disk. + +In general, it is preferable to not block the host unless necessary. We want to +feed the GPU and the host with useful work to do. Waiting on fences to signal +is not useful work. Thus we prefer semaphores, or other synchronization +primitives not yet covered, to synchronize our work. + +Fences must be reset manually to put them back into the unsignaled state. This +is because fences are used to control the execution of the host, and so the +host gets to decide when to reset the fence. Contrast this to semaphores which +are used to order work on the GPU without the host being involved. + +In summary, semaphores are used to specify the execution order of operations on +the GPU while fences are used to keep the CPU and GPU in sync with each-other. + +### What to choose? + +We have two synchronization primitives to use and conveniently two places to +apply synchronization: Swapchain operations and waiting for the previous frame +to finish. We want to use semaphores for swapchain operations because they +happen on the GPU, thus we don't want to make the host wait around if we can +help it. For waiting on the previous frame to finish, we want to use fences +for the opposite reason, because we need the host to wait. This is so we don't +draw more than one frame at a time. Because we re-record the command buffer +every frame, we cannot record the next frame's work to the command buffer +until the current frame has finished executing, as we don't want to overwrite +the current contents of the command buffer while the GPU is using it. + +## Creating the synchronization objects + +We'll need one semaphore to signal that an image has been acquired from the +swapchain and is ready for rendering, another one to signal that rendering has +finished and presentation can happen, and a fence to make sure only one frame +is rendering at a time. + +Create three class members to store these semaphore objects and fence object: + +```c++ +VkSemaphore imageAvailableSemaphore; +VkSemaphore renderFinishedSemaphore; +VkFence inFlightFence; +``` + +To create the semaphores, we'll add the last `create` function for this part of +the tutorial: `createSyncObjects`: + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffer(); + createSyncObjects(); +} + +... + +void createSyncObjects() { + +} +``` + +Creating semaphores requires filling in the `VkSemaphoreCreateInfo`, but in the +current version of the API it doesn't actually have any required fields besides +`sType`: + +```c++ +void createSyncObjects() { + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; +} +``` + +Future versions of the Vulkan API or extensions may add functionality for the +`flags` and `pNext` parameters like it does for the other structures. + +Creating a fence requires filling in the `VkFenceCreateInfo`: + +```c++ +VkFenceCreateInfo fenceInfo{}; +fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; +``` + +Creating the semaphores and fence follows the familiar pattern with +`vkCreateSemaphore` & `vkCreateFence`: + +```c++ +if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence) != VK_SUCCESS) { + throw std::runtime_error("failed to create semaphores!"); +} +``` + +The semaphores and fence should be cleaned up at the end of the program, when +all commands have finished and no more synchronization is necessary: + +```c++ +void cleanup() { + vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); + vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); + vkDestroyFence(device, inFlightFence, nullptr); +``` + +Onto the main drawing function! + +## Waiting for the previous frame + +At the start of the frame, we want to wait until the previous frame has +finished, so that the command buffer and semaphores are available to use. To do +that, we call `vkWaitForFences`: + +```c++ +void drawFrame() { + vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX); +} +``` + +The `vkWaitForFences` function takes an array of fences and waits on the host +for either any or all of the fences to be signaled before returning. The +`VK_TRUE` we pass here indicates that we want to wait for all fences, but in +the case of a single one it doesn't matter. This function also has a timeout +parameter that we set to the maximum value of a 64 bit unsigned integer, +`UINT64_MAX`, which effectively disables the timeout. + +After waiting, we need to manually reset the fence to the unsignaled state with +the `vkResetFences` call: +```c++ + vkResetFences(device, 1, &inFlightFence); +``` + +Before we can proceed, there is a slight hiccup in our design. On the first +frame we call `drawFrame()`, which immediately waits on `inFlightFence` to +be signaled. `inFlightFence` is only signaled after a frame has finished +rendering, yet since this is the first frame, there are no previous frames in +which to signal the fence! Thus `vkWaitForFences()` blocks indefinitely, +waiting on something which will never happen. + +Of the many solutions to this dilemma, there is a clever workaround built into +the API. Create the fence in the signaled state, so that the first call to +`vkWaitForFences()` returns immediately since the fence is already signaled. + +To do this, we add the `VK_FENCE_CREATE_SIGNALED_BIT` flag to the `VkFenceCreateInfo`: + +```c++ +void createSyncObjects() { + ... + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + ... +} +``` + +## Acquiring an image from the swap chain + +The next thing we need to do in the `drawFrame` function is acquire an image +from the swap chain. Recall that the swap chain is an extension feature, so we +must use a function with the `vk*KHR` naming convention: + +```c++ +void drawFrame() { + ... + + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); +} +``` + +The first two parameters of `vkAcquireNextImageKHR` are the logical device and +the swap chain from which we wish to acquire an image. The third parameter +specifies a timeout in nanoseconds for an image to become available. Using the +maximum value of a 64 bit unsigned integer means we effectively disable the +timeout. + +The next two parameters specify synchronization objects that are to be signaled +when the presentation engine is finished using the image. That's the point in +time where we can start drawing to it. It is possible to specify a semaphore, +fence or both. We're going to use our `imageAvailableSemaphore` for that purpose +here. + +The last parameter specifies a variable to output the index of the swap chain +image that has become available. The index refers to the `VkImage` in our +`swapChainImages` array. We're going to use that index to pick the `VkFrameBuffer`. + +## Recording the command buffer + +With the imageIndex specifying the swap chain image to use in hand, we can now +record the command buffer. First, we call `vkResetCommandBuffer` on the command +buffer to make sure it is able to be recorded. + +```c++ +vkResetCommandBuffer(commandBuffer, 0); +``` + +The second parameter of `vkResetCommandBuffer` is a `VkCommandBufferResetFlagBits` +flag. Since we don't want to do anything special, we leave it as 0. + +Now call the function `recordCommandBuffer` to record the commands we want. + +```c++ +recordCommandBuffer(commandBuffer, imageIndex); +``` + +With a fully recorded command buffer, we can now submit it. + +## Submitting the command buffer + +Queue submission and synchronization is configured through parameters in the +`VkSubmitInfo` structure. + +```c++ +VkSubmitInfo submitInfo{}; +submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + +VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; +VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; +submitInfo.waitSemaphoreCount = 1; +submitInfo.pWaitSemaphores = waitSemaphores; +submitInfo.pWaitDstStageMask = waitStages; +``` + +The first three parameters specify which semaphores to wait on before execution +begins and in which stage(s) of the pipeline to wait. We want to wait with +writing colors to the image until it's available, so we're specifying the stage +of the graphics pipeline that writes to the color attachment. That means that +theoretically the implementation can already start executing our vertex shader +and such while the image is not yet available. Each entry in the `waitStages` +array corresponds to the semaphore with the same index in `pWaitSemaphores`. + +```c++ +submitInfo.commandBufferCount = 1; +submitInfo.pCommandBuffers = &commandBuffer; +``` + +The next two parameters specify which command buffers to actually submit for +execution. We simply submit the single command buffer we have. + +```c++ +VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; +submitInfo.signalSemaphoreCount = 1; +submitInfo.pSignalSemaphores = signalSemaphores; +``` + +The `signalSemaphoreCount` and `pSignalSemaphores` parameters specify which +semaphores to signal once the command buffer(s) have finished execution. In our +case we're using the `renderFinishedSemaphore` for that purpose. + +```c++ +if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); +} +``` + +We can now submit the command buffer to the graphics queue using +`vkQueueSubmit`. The function takes an array of `VkSubmitInfo` structures as +argument for efficiency when the workload is much larger. The last parameter +references an optional fence that will be signaled when the command buffers +finish execution. This allows us to know when it is safe for the command +buffer to be reused, thus we want to give it `inFlightFence`. Now on the next +frame, the CPU will wait for this command buffer to finish executing before it +records new commands into it. + +## Subpass dependencies + +Remember that the subpasses in a render pass automatically take care of image +layout transitions. These transitions are controlled by *subpass dependencies*, +which specify memory and execution dependencies between subpasses. We have only +a single subpass right now, but the operations right before and right after this +subpass also count as implicit "subpasses". + +There are two built-in dependencies that take care of the transition at the +start of the render pass and at the end of the render pass, but the former does +not occur at the right time. It assumes that the transition occurs at the start +of the pipeline, but we haven't acquired the image yet at that point! There are +two ways to deal with this problem. We could change the `waitStages` for the +`imageAvailableSemaphore` to `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` to ensure that +the render passes don't begin until the image is available, or we can make the +render pass wait for the `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage. +I've decided to go with the second option here, because it's a good excuse to +have a look at subpass dependencies and how they work. + +Subpass dependencies are specified in `VkSubpassDependency` structs. Go to the +`createRenderPass` function and add one: + +```c++ +VkSubpassDependency dependency{}; +dependency.srcSubpass = VK_SUBPASS_EXTERNAL; +dependency.dstSubpass = 0; +``` + +The first two fields specify the indices of the dependency and the dependent +subpass. The special value `VK_SUBPASS_EXTERNAL` refers to the implicit subpass +before or after the render pass depending on whether it is specified in +`srcSubpass` or `dstSubpass`. The index `0` refers to our subpass, which is the +first and only one. The `dstSubpass` must always be higher than `srcSubpass` to +prevent cycles in the dependency graph (unless one of the subpasses is +`VK_SUBPASS_EXTERNAL`). + +```c++ +dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +dependency.srcAccessMask = 0; +``` + +The next two fields specify the operations to wait on and the stages in which +these operations occur. We need to wait for the swap chain to finish reading +from the image before we can access it. This can be accomplished by waiting on +the color attachment output stage itself. + +```c++ +dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; +``` + +The operations that should wait on this are in the color attachment stage and +involve the writing of the color attachment. These settings will +prevent the transition from happening until it's actually necessary (and +allowed): when we want to start writing colors to it. + +```c++ +renderPassInfo.dependencyCount = 1; +renderPassInfo.pDependencies = &dependency; +``` + +The `VkRenderPassCreateInfo` struct has two fields to specify an array of +dependencies. + +## Presentation + +The last step of drawing a frame is submitting the result back to the swap chain +to have it eventually show up on the screen. Presentation is configured through +a `VkPresentInfoKHR` structure at the end of the `drawFrame` function. + +```c++ +VkPresentInfoKHR presentInfo{}; +presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + +presentInfo.waitSemaphoreCount = 1; +presentInfo.pWaitSemaphores = signalSemaphores; +``` + +The first two parameters specify which semaphores to wait on before presentation +can happen, just like `VkSubmitInfo`. Since we want to wait on the command buffer +to finish execution, thus our triangle being drawn, we take the semaphores +which will be signalled and wait on them, thus we use `signalSemaphores`. + + +```c++ +VkSwapchainKHR swapChains[] = {swapChain}; +presentInfo.swapchainCount = 1; +presentInfo.pSwapchains = swapChains; +presentInfo.pImageIndices = &imageIndex; +``` + +The next two parameters specify the swap chains to present images to and the +index of the image for each swap chain. This will almost always be a single one. + +```c++ +presentInfo.pResults = nullptr; // Optional +``` + +There is one last optional parameter called `pResults`. It allows you to specify +an array of `VkResult` values to check for every individual swap chain if +presentation was successful. It's not necessary if you're only using a single +swap chain, because you can simply use the return value of the present function. + +```c++ +vkQueuePresentKHR(presentQueue, &presentInfo); +``` + +The `vkQueuePresentKHR` function submits the request to present an image to the +swap chain. We'll add error handling for both `vkAcquireNextImageKHR` and +`vkQueuePresentKHR` in the next chapter, because their failure does not +necessarily mean that the program should terminate, unlike the functions we've +seen so far. + +If you did everything correctly up to this point, then you should now see +something resembling the following when you run your program: + +![](/images/triangle.png) + +>This colored triangle may look a bit different from the one you're used to seeing in graphics tutorials. That's because this tutorial lets the shader interpolate in linear color space and converts to sRGB color space afterwards. See [this blog post](https://medium.com/@heypete/hello-triangle-meet-swift-and-wide-color-6f9e246616d9) for a discussion of the difference. + +Yay! Unfortunately, you'll see that when validation layers are enabled, the +program crashes as soon as you close it. The messages printed to the terminal +from `debugCallback` tell us why: + +![](/images/semaphore_in_use.png) + +Remember that all of the operations in `drawFrame` are asynchronous. That means +that when we exit the loop in `mainLoop`, drawing and presentation operations +may still be going on. Cleaning up resources while that is happening is a bad +idea. + +To fix that problem, we should wait for the logical device to finish operations +before exiting `mainLoop` and destroying the window: + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); +} +``` + +You can also wait for operations in a specific command queue to be finished with +`vkQueueWaitIdle`. These functions can be used as a very rudimentary way to +perform synchronization. You'll see that the program now exits without problems +when closing the window. + +## Conclusion + +A little over 900 lines of code later, we've finally gotten to the stage of seeing +something pop up on the screen! Bootstrapping a Vulkan program is definitely a +lot of work, but the take-away message is that Vulkan gives you an immense +amount of control through its explicitness. I recommend you to take some time +now to reread the code and build a mental model of the purpose of all of the +Vulkan objects in the program and how they relate to each other. We'll be +building on top of that knowledge to extend the functionality of the program +from this point on. + +The next chapter will expand the render loop to handle multiple frames in flight. + +[C++ code](/code/15_hello_triangle.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md b/en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md new file mode 100644 index 00000000..e2345e31 --- /dev/null +++ b/en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md @@ -0,0 +1,176 @@ +## Frames in flight + +Right now our render loop has one glaring flaw. We are required to wait on the +previous frame to finish before we can start rendering the next which results +in unnecessary idling of the host. + + + +The way to fix this is to allow multiple frames to be *in-flight* at once, that +is to say, allow the rendering of one frame to not interfere with the recording +of the next. How do we do this? Any resource that is accessed and modified +during rendering must be duplicated. Thus, we need multiple command buffers, +semaphores, and fences. In later chapters we will also add multiple instances +of other resources, so we will see this concept reappear. + +Start by adding a constant at the top of the program that defines how many +frames should be processed concurrently: + +```c++ +const int MAX_FRAMES_IN_FLIGHT = 2; +``` + +We choose the number 2 because we don't want the CPU to get *too* far ahead of +the GPU. With 2 frames in flight, the CPU and the GPU can be working on their +own tasks at the same time. If the CPU finishes early, it will wait till the +GPU finishes rendering before submitting more work. With 3 or more frames in +flight, the CPU could get ahead of the GPU, adding frames of latency. +Generally, extra latency isn't desired. But giving the application control over +the number of frames in flight is another example of Vulkan being explicit. + +Each frame should have its own command buffer, set of semaphores, and fence. +Rename and then change them to be `std::vector`s of the objects: + +```c++ +std::vector commandBuffers; + +... + +std::vector imageAvailableSemaphores; +std::vector renderFinishedSemaphores; +std::vector inFlightFences; +``` + +Then we need to create multiple command buffers. Rename `createCommandBuffer` +to `createCommandBuffers`. Next we need to resize the command buffers vector +to the size of `MAX_FRAMES_IN_FLIGHT`, alter the `VkCommandBufferAllocateInfo` +to contain that many command buffers, and then change the destination to our +vector of command buffers: + +```c++ +void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + ... + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } +} +``` + +The `createSyncObjects` function should be changed to create all of the objects: + +```c++ +void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } +} +``` + +Similarly, they should also all be cleaned up: + +```c++ +void cleanup() { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + ... +} +``` + +Remember, because command buffers are freed for us when we free the command +pool, there is nothing extra to do for command buffer cleanup. + +To use the right objects every frame, we need to keep track of the current +frame. We will use a frame index for that purpose: + +```c++ +uint32_t currentFrame = 0; +``` + +The `drawFrame` function can now be modified to use the right objects: + +```c++ +void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + ... + + vkResetCommandBuffer(commandBuffers[currentFrame], 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + ... + + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + ... + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + + ... + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + + ... + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { +} +``` + +Of course, we shouldn't forget to advance to the next frame every time: + +```c++ +void drawFrame() { + ... + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +} +``` + +By using the modulo (%) operator, we ensure that the frame index loops around +after every `MAX_FRAMES_IN_FLIGHT` enqueued frames. + + + +We've now implemented all the needed synchronization to ensure that there are +no more than `MAX_FRAMES_IN_FLIGHT` frames of work enqueued and that these +frames are not stepping over eachother. Note that it is fine for other parts of +the code, like the final cleanup, to rely on more rough synchronization like +`vkDeviceWaitIdle`. You should decide on which approach to use based on +performance requirements. + +To learn more about synchronization through examples, have a look at [this extensive overview](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#swapchain-image-acquire-and-present) by Khronos. + + +In the next chapter we'll deal with one more small thing that is required for a +well-behaved Vulkan program. + + +[C++ code](/code/16_frames_in_flight.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/03_Drawing_a_triangle/04_Swap_chain_recreation.md b/en/03_Drawing_a_triangle/04_Swap_chain_recreation.md new file mode 100644 index 00000000..ce58528b --- /dev/null +++ b/en/03_Drawing_a_triangle/04_Swap_chain_recreation.md @@ -0,0 +1,280 @@ +## Introduction + +The application we have now successfully draws a triangle, but there are some +circumstances that it isn't handling properly yet. It is possible for the window +surface to change such that the swap chain is no longer compatible with it. One +of the reasons that could cause this to happen is the size of the window +changing. We have to catch these events and recreate the swap chain. + +## Recreating the swap chain + +Create a new `recreateSwapChain` function that calls `createSwapChain` and all +of the creation functions for the objects that depend on the swap chain or the +window size. + +```c++ +void recreateSwapChain() { + vkDeviceWaitIdle(device); + + createSwapChain(); + createImageViews(); + createFramebuffers(); +} +``` + +We first call `vkDeviceWaitIdle`, because just like in the last chapter, we +shouldn't touch resources that may still be in use. Obviously, we'll have to recreate +the swap chain itself. The image views need to be recreated because they are based +directly on the swap chain images. Finally, the framebuffers directly depend on the +swap chain images, and thus must be recreated as well. + +To make sure that the old versions of these objects are cleaned up before +recreating them, we should move some of the cleanup code to a separate function +that we can call from the `recreateSwapChain` function. Let's call it +`cleanupSwapChain`: + +```c++ +void cleanupSwapChain() { + +} + +void recreateSwapChain() { + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); +} +``` + +Note that we don't recreate the renderpass here for simplicity. In theory it can be possible for the swap chain image format to change during an applications' lifetime, e.g. when moving a window from a standard range to a high dynamic range monitor. This may require the application to recreate the renderpass to make sure the change between dynamic ranges is properly reflected. + +We'll move the cleanup code of all objects that are recreated as part of a swap +chain refresh from `cleanup` to `cleanupSwapChain`: + +```c++ +void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); +} + +void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +Note that in `chooseSwapExtent` we already query the new window resolution to +make sure that the swap chain images have the (new) right size, so there's no +need to modify `chooseSwapExtent` (remember that we already had to use +`glfwGetFramebufferSize` to get the resolution of the surface in pixels when +creating the swap chain). + +That's all it takes to recreate the swap chain! However, the disadvantage of +this approach is that we need to stop all rendering before creating the new swap +chain. It is possible to create a new swap chain while drawing commands on an +image from the old swap chain are still in-flight. You need to pass the previous +swap chain to the `oldSwapChain` field in the `VkSwapchainCreateInfoKHR` struct +and destroy the old swap chain as soon as you've finished using it. + +## Suboptimal or out-of-date swap chain + +Now we just need to figure out when swap chain recreation is necessary and call +our new `recreateSwapChain` function. Luckily, Vulkan will usually just tell us that the swap chain is no longer adequate during presentation. The `vkAcquireNextImageKHR` and +`vkQueuePresentKHR` functions can return the following special values to +indicate this. + +* `VK_ERROR_OUT_OF_DATE_KHR`: The swap chain has become incompatible with the +surface and can no longer be used for rendering. Usually happens after a window resize. +* `VK_SUBOPTIMAL_KHR`: The swap chain can still be used to successfully present +to the surface, but the surface properties are no longer matched exactly. + +```c++ +VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + +if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; +} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); +} +``` + +If the swap chain turns out to be out of date when attempting to acquire an +image, then it is no longer possible to present to it. Therefore we should +immediately recreate the swap chain and try again in the next `drawFrame` call. + +You could also decide to do that if the swap chain is suboptimal, but I've +chosen to proceed anyway in that case because we've already acquired an image. +Both `VK_SUCCESS` and `VK_SUBOPTIMAL_KHR` are considered "success" return codes. + +```c++ +result = vkQueuePresentKHR(presentQueue, &presentInfo); + +if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + recreateSwapChain(); +} else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); +} + +currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +``` + +The `vkQueuePresentKHR` function returns the same values with the same meaning. +In this case we will also recreate the swap chain if it is suboptimal, because +we want the best possible result. + +## Fixing a deadlock + +If we try to run the code now, it is possible to encounter a deadlock. +Debugging the code, we find that the application reaches `vkWaitForFences` but +never continues past it. This is because when `vkAcquireNextImageKHR` returns +`VK_ERROR_OUT_OF_DATE_KHR`, we recreate the swapchain and then return from +`drawFrame`. But before that happens, the current frame's fence was waited upon +and reset. Since we return immediately, no work is submitted for execution and +the fence will never be signaled, causing `vkWaitForFences` to halt forever. + +There is a simple fix thankfully. Delay resetting the fence until after we +know for sure we will be submitting work with it. Thus, if we return early, the +fence is still signaled and `vkWaitForFences` wont deadlock the next time we +use the same fence object. + +The beginning of `drawFrame` should now look like this: +```c++ +vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + +uint32_t imageIndex; +VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + +if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; +} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); +} + +// Only reset the fence if we are submitting work +vkResetFences(device, 1, &inFlightFences[currentFrame]); +``` + +## Handling resizes explicitly + +Although many drivers and platforms trigger `VK_ERROR_OUT_OF_DATE_KHR` automatically after a window resize, it is not guaranteed to happen. That's why we'll add some extra code to also handle resizes explicitly. First add a new member variable that flags that a resize has happened: + +```c++ +std::vector inFlightFences; + +bool framebufferResized = false; +``` + +The `drawFrame` function should then be modified to also check for this flag: + +```c++ +if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); +} else if (result != VK_SUCCESS) { + ... +} +``` + +It is important to do this after `vkQueuePresentKHR` to ensure that the semaphores are in a consistent state, otherwise a signaled semaphore may never be properly waited upon. Now to actually detect resizes we can use the `glfwSetFramebufferSizeCallback` function in the GLFW framework to set up a callback: + +```c++ +void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); +} + +static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + +} +``` + +The reason that we're creating a `static` function as a callback is because GLFW does not know how to properly call a member function with the right `this` pointer to our `HelloTriangleApplication` instance. + +However, we do get a reference to the `GLFWwindow` in the callback and there is another GLFW function that allows you to store an arbitrary pointer inside of it: `glfwSetWindowUserPointer`: + +```c++ +window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +glfwSetWindowUserPointer(window, this); +glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); +``` + +This value can now be retrieved from within the callback with `glfwGetWindowUserPointer` to properly set the flag: + +```c++ +static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; +} +``` + +Now try to run the program and resize the window to see if the framebuffer is indeed resized properly with the window. + +## Handling minimization + +There is another case where a swap chain may become out of date and that is a special kind of window resizing: window minimization. This case is special because it will result in a frame buffer size of `0`. In this tutorial we will handle that by pausing until the window is in the foreground again by extending the `recreateSwapChain` function: + +```c++ +void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + ... +} +``` + +The initial call to `glfwGetFramebufferSize` handles the case where the size is already correct and `glfwWaitEvents` would have nothing to wait on. + +Congratulations, you've now finished your very first well-behaved Vulkan +program! In the next chapter we're going to get rid of the hardcoded vertices in +the vertex shader and actually use a vertex buffer. + +[C++ code](/code/17_swap_chain_recreation.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/04_Vertex_buffers/00_Vertex_input_description.md b/en/04_Vertex_buffers/00_Vertex_input_description.md new file mode 100644 index 00000000..e7da3e4f --- /dev/null +++ b/en/04_Vertex_buffers/00_Vertex_input_description.md @@ -0,0 +1,225 @@ +## Introduction + +In the next few chapters, we're going to replace the hardcoded vertex data in +the vertex shader with a vertex buffer in memory. We'll start with the easiest +approach of creating a CPU visible buffer and using `memcpy` to copy the vertex +data into it directly, and after that we'll see how to use a staging buffer to +copy the vertex data to high performance memory. + +## Vertex shader + +First change the vertex shader to no longer include the vertex data in the +shader code itself. The vertex shader takes input from a vertex buffer using the +`in` keyword. + +```glsl +#version 450 + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} +``` + +The `inPosition` and `inColor` variables are *vertex attributes*. They're +properties that are specified per-vertex in the vertex buffer, just like we +manually specified a position and color per vertex using the two arrays. Make +sure to recompile the vertex shader! + +Just like `fragColor`, the `layout(location = x)` annotations assign indices to +the inputs that we can later use to reference them. It is important to know that +some types, like `dvec3` 64 bit vectors, use multiple *slots*. That means that +the index after it must be at least 2 higher: + +```glsl +layout(location = 0) in dvec3 inPosition; +layout(location = 2) in vec3 inColor; +``` + +You can find more info about the layout qualifier in the [OpenGL wiki](https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)). + +## Vertex data + +We're moving the vertex data from the shader code to an array in the code of our +program. Start by including the GLM library, which provides us with linear +algebra related types like vectors and matrices. We're going to use these types +to specify the position and color vectors. + +```c++ +#include +``` + +Create a new structure called `Vertex` with the two attributes that we're going +to use in the vertex shader inside it: + +```c++ +struct Vertex { + glm::vec2 pos; + glm::vec3 color; +}; +``` + +GLM conveniently provides us with C++ types that exactly match the vector types +used in the shader language. + +```c++ +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; +``` + +Now use the `Vertex` structure to specify an array of vertex data. We're using +exactly the same position and color values as before, but now they're combined +into one array of vertices. This is known as *interleaving* vertex attributes. + +## Binding descriptions + +The next step is to tell Vulkan how to pass this data format to the vertex +shader once it's been uploaded into GPU memory. There are two types of +structures needed to convey this information. + +The first structure is `VkVertexInputBindingDescription` and we'll add a member +function to the `Vertex` struct to populate it with the right data. + +```c++ +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + + return bindingDescription; + } +}; +``` + +A vertex binding describes at which rate to load data from memory throughout the +vertices. It specifies the number of bytes between data entries and whether to +move to the next data entry after each vertex or after each instance. + +```c++ +VkVertexInputBindingDescription bindingDescription{}; +bindingDescription.binding = 0; +bindingDescription.stride = sizeof(Vertex); +bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; +``` + +All of our per-vertex data is packed together in one array, so we're only going +to have one binding. The `binding` parameter specifies the index of the binding +in the array of bindings. The `stride` parameter specifies the number of bytes +from one entry to the next, and the `inputRate` parameter can have one of the +following values: + +* `VK_VERTEX_INPUT_RATE_VERTEX`: Move to the next data entry after each vertex +* `VK_VERTEX_INPUT_RATE_INSTANCE`: Move to the next data entry after each +instance + +We're not going to use instanced rendering, so we'll stick to per-vertex data. + +## Attribute descriptions + +The second structure that describes how to handle vertex input is +`VkVertexInputAttributeDescription`. We're going to add another helper function +to `Vertex` to fill in these structs. + +```c++ +#include + +... + +static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + return attributeDescriptions; +} +``` + +As the function prototype indicates, there are going to be two of these +structures. An attribute description struct describes how to extract a vertex +attribute from a chunk of vertex data originating from a binding description. We +have two attributes, position and color, so we need two attribute description +structs. + +```c++ +attributeDescriptions[0].binding = 0; +attributeDescriptions[0].location = 0; +attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; +attributeDescriptions[0].offset = offsetof(Vertex, pos); +``` + +The `binding` parameter tells Vulkan from which binding the per-vertex data +comes. The `location` parameter references the `location` directive of the +input in the vertex shader. The input in the vertex shader with location `0` is +the position, which has two 32-bit float components. + +The `format` parameter describes the type of data for the attribute. A bit +confusingly, the formats are specified using the same enumeration as color +formats. The following shader types and formats are commonly used together: + +* `float`: `VK_FORMAT_R32_SFLOAT` +* `vec2`: `VK_FORMAT_R32G32_SFLOAT` +* `vec3`: `VK_FORMAT_R32G32B32_SFLOAT` +* `vec4`: `VK_FORMAT_R32G32B32A32_SFLOAT` + +As you can see, you should use the format where the amount of color channels +matches the number of components in the shader data type. It is allowed to use +more channels than the number of components in the shader, but they will be +silently discarded. If the number of channels is lower than the number of +components, then the BGA components will use default values of `(0, 0, 1)`. The +color type (`SFLOAT`, `UINT`, `SINT`) and bit width should also match the type +of the shader input. See the following examples: + +* `ivec2`: `VK_FORMAT_R32G32_SINT`, a 2-component vector of 32-bit signed +integers +* `uvec4`: `VK_FORMAT_R32G32B32A32_UINT`, a 4-component vector of 32-bit +unsigned integers +* `double`: `VK_FORMAT_R64_SFLOAT`, a double-precision (64-bit) float + +The `format` parameter implicitly defines the byte size of attribute data and +the `offset` parameter specifies the number of bytes since the start of the +per-vertex data to read from. The binding is loading one `Vertex` at a time and +the position attribute (`pos`) is at an offset of `0` bytes from the beginning +of this struct. This is automatically calculated using the `offsetof` macro. + +```c++ +attributeDescriptions[1].binding = 0; +attributeDescriptions[1].location = 1; +attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; +attributeDescriptions[1].offset = offsetof(Vertex, color); +``` + +The color attribute is described in much the same way. + +## Pipeline vertex input + +We now need to set up the graphics pipeline to accept vertex data in this format +by referencing the structures in `createGraphicsPipeline`. Find the +`vertexInputInfo` struct and modify it to reference the two descriptions: + +```c++ +auto bindingDescription = Vertex::getBindingDescription(); +auto attributeDescriptions = Vertex::getAttributeDescriptions(); + +vertexInputInfo.vertexBindingDescriptionCount = 1; +vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); +vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; +vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); +``` + +The pipeline is now ready to accept vertex data in the format of the `vertices` +container and pass it on to our vertex shader. If you run the program now with +validation layers enabled, you'll see that it complains that there is no vertex +buffer bound to the binding. The next step is to create a vertex buffer and move +the vertex data to it so the GPU is able to access it. + +[C++ code](/code/18_vertex_input.cpp) / +[Vertex shader](/code/18_shader_vertexbuffer.vert) / +[Fragment shader](/code/18_shader_vertexbuffer.frag) diff --git a/en/04_Vertex_buffers/01_Vertex_buffer_creation.md b/en/04_Vertex_buffers/01_Vertex_buffer_creation.md new file mode 100644 index 00000000..77122c50 --- /dev/null +++ b/en/04_Vertex_buffers/01_Vertex_buffer_creation.md @@ -0,0 +1,342 @@ +## Introduction + +Buffers in Vulkan are regions of memory used for storing arbitrary data that can +be read by the graphics card. They can be used to store vertex data, which we'll +do in this chapter, but they can also be used for many other purposes that we'll +explore in future chapters. Unlike the Vulkan objects we've been dealing with so +far, buffers do not automatically allocate memory for themselves. The work from +the previous chapters has shown that the Vulkan API puts the programmer in +control of almost everything and memory management is one of those things. + +## Buffer creation + +Create a new function `createVertexBuffer` and call it from `initVulkan` right +before `createCommandBuffers`. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); +} + +... + +void createVertexBuffer() { + +} +``` + +Creating a buffer requires us to fill a `VkBufferCreateInfo` structure. + +```c++ +VkBufferCreateInfo bufferInfo{}; +bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; +bufferInfo.size = sizeof(vertices[0]) * vertices.size(); +``` + +The first field of the struct is `size`, which specifies the size of the buffer +in bytes. Calculating the byte size of the vertex data is straightforward with +`sizeof`. + +```c++ +bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; +``` + +The second field is `usage`, which indicates for which purposes the data in the +buffer is going to be used. It is possible to specify multiple purposes using a +bitwise or. Our use case will be a vertex buffer, we'll look at other types of +usage in future chapters. + +```c++ +bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; +``` + +Just like the images in the swap chain, buffers can also be owned by a specific +queue family or be shared between multiple at the same time. The buffer will +only be used from the graphics queue, so we can stick to exclusive access. + +The `flags` parameter is used to configure sparse buffer memory, which is not +relevant right now. We'll leave it at the default value of `0`. + +We can now create the buffer with `vkCreateBuffer`. Define a class member to +hold the buffer handle and call it `vertexBuffer`. + +```c++ +VkBuffer vertexBuffer; + +... + +void createVertexBuffer() { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(vertices[0]) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create vertex buffer!"); + } +} +``` + +The buffer should be available for use in rendering commands until the end of +the program and it does not depend on the swap chain, so we'll clean it up in +the original `cleanup` function: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + + ... +} +``` + +## Memory requirements + +The buffer has been created, but it doesn't actually have any memory assigned to +it yet. The first step of allocating memory for the buffer is to query its +memory requirements using the aptly named `vkGetBufferMemoryRequirements` +function. + +```c++ +VkMemoryRequirements memRequirements; +vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements); +``` + +The `VkMemoryRequirements` struct has three fields: + +* `size`: The size of the required amount of memory in bytes, may differ from +`bufferInfo.size`. +* `alignment`: The offset in bytes where the buffer begins in the allocated +region of memory, depends on `bufferInfo.usage` and `bufferInfo.flags`. +* `memoryTypeBits`: Bit field of the memory types that are suitable for the +buffer. + +Graphics cards can offer different types of memory to allocate from. Each type +of memory varies in terms of allowed operations and performance characteristics. +We need to combine the requirements of the buffer and our own application +requirements to find the right type of memory to use. Let's create a new +function `findMemoryType` for this purpose. + +```c++ +uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + +} +``` + +First we need to query info about the available types of memory using +`vkGetPhysicalDeviceMemoryProperties`. + +```c++ +VkPhysicalDeviceMemoryProperties memProperties; +vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); +``` + +The `VkPhysicalDeviceMemoryProperties` structure has two arrays `memoryTypes` +and `memoryHeaps`. Memory heaps are distinct memory resources like dedicated +VRAM and swap space in RAM for when VRAM runs out. The different types of memory +exist within these heaps. Right now we'll only concern ourselves with the type +of memory and not the heap it comes from, but you can imagine that this can +affect performance. + +Let's first find a memory type that is suitable for the buffer itself: + +```c++ +for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if (typeFilter & (1 << i)) { + return i; + } +} + +throw std::runtime_error("failed to find suitable memory type!"); +``` + +The `typeFilter` parameter will be used to specify the bit field of memory types +that are suitable. That means that we can find the index of a suitable memory +type by simply iterating over them and checking if the corresponding bit is set +to `1`. + +However, we're not just interested in a memory type that is suitable for the +vertex buffer. We also need to be able to write our vertex data to that memory. +The `memoryTypes` array consists of `VkMemoryType` structs that specify the heap +and properties of each type of memory. The properties define special features +of the memory, like being able to map it so we can write to it from the CPU. +This property is indicated with `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, but we +also need to use the `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` property. We'll see +why when we map the memory. + +We can now modify the loop to also check for the support of this property: + +```c++ +for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } +} +``` + +We may have more than one desirable property, so we should check if the result +of the bitwise AND is not just non-zero, but equal to the desired properties bit +field. If there is a memory type suitable for the buffer that also has all of +the properties we need, then we return its index, otherwise we throw an +exception. + +## Memory allocation + +We now have a way to determine the right memory type, so we can actually +allocate the memory by filling in the `VkMemoryAllocateInfo` structure. + +```c++ +VkMemoryAllocateInfo allocInfo{}; +allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; +allocInfo.allocationSize = memRequirements.size; +allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); +``` + +Memory allocation is now as simple as specifying the size and type, both of +which are derived from the memory requirements of the vertex buffer and the +desired property. Create a class member to store the handle to the memory and +allocate it with `vkAllocateMemory`. + +```c++ +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; + +... + +if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate vertex buffer memory!"); +} +``` + +If memory allocation was successful, then we can now associate this memory with +the buffer using `vkBindBufferMemory`: + +```c++ +vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0); +``` + +The first three parameters are self-explanatory and the fourth parameter is the +offset within the region of memory. Since this memory is allocated specifically +for this the vertex buffer, the offset is simply `0`. If the offset is non-zero, +then it is required to be divisible by `memRequirements.alignment`. + +Of course, just like dynamic memory allocation in C++, the memory should be +freed at some point. Memory that is bound to a buffer object may be freed once +the buffer is no longer used, so let's free it after the buffer has been +destroyed: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); +``` + +## Filling the vertex buffer + +It is now time to copy the vertex data to the buffer. This is done by [mapping +the buffer memory](https://en.wikipedia.org/wiki/Memory-mapped_I/O) into CPU +accessible memory with `vkMapMemory`. + +```c++ +void* data; +vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data); +``` + +This function allows us to access a region of the specified memory resource +defined by an offset and size. The offset and size here are `0` and +`bufferInfo.size`, respectively. It is also possible to specify the special +value `VK_WHOLE_SIZE` to map all of the memory. The second to last parameter can +be used to specify flags, but there aren't any available yet in the current API. +It must be set to the value `0`. The last parameter specifies the output for the +pointer to the mapped memory. + +```c++ +void* data; +vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferInfo.size); +vkUnmapMemory(device, vertexBufferMemory); +``` + +You can now simply `memcpy` the vertex data to the mapped memory and unmap it +again using `vkUnmapMemory`. Unfortunately the driver may not immediately copy +the data into the buffer memory, for example because of caching. It is also +possible that writes to the buffer are not visible in the mapped memory yet. +There are two ways to deal with that problem: + +* Use a memory heap that is host coherent, indicated with +`VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` +* Call `vkFlushMappedMemoryRanges` after writing to the mapped memory, and +call `vkInvalidateMappedMemoryRanges` before reading from the mapped memory + +We went for the first approach, which ensures that the mapped memory always +matches the contents of the allocated memory. Do keep in mind that this may lead +to slightly worse performance than explicit flushing, but we'll see why that +doesn't matter in the next chapter. + +Flushing memory ranges or using a coherent memory heap means that the driver will be aware of our writes to the buffer, but it doesn't mean that they are actually visible on the GPU yet. The transfer of data to the GPU is an operation that happens in the background and the specification simply [tells us](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-submission-host-writes) that it is guaranteed to be complete as of the next call to `vkQueueSubmit`. + +## Binding the vertex buffer + +All that remains now is binding the vertex buffer during rendering operations. +We're going to extend the `recordCommandBuffer` function to do that. + +```c++ +vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + +VkBuffer vertexBuffers[] = {vertexBuffer}; +VkDeviceSize offsets[] = {0}; +vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + +vkCmdDraw(commandBuffer, static_cast(vertices.size()), 1, 0, 0); +``` + +The `vkCmdBindVertexBuffers` function is used to bind vertex buffers to +bindings, like the one we set up in the previous chapter. The first two +parameters, besides the command buffer, specify the offset and number of +bindings we're going to specify vertex buffers for. The last two parameters +specify the array of vertex buffers to bind and the byte offsets to start +reading vertex data from. You should also change the call to `vkCmdDraw` to pass +the number of vertices in the buffer as opposed to the hardcoded number `3`. + +Now run the program and you should see the familiar triangle again: + +![](/images/triangle.png) + +Try changing the color of the top vertex to white by modifying the `vertices` +array: + +```c++ +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 1.0f, 1.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; +``` + +Run the program again and you should see the following: + +![](/images/triangle_white.png) + +In the next chapter we'll look at a different way to copy vertex data to a +vertex buffer that results in better performance, but takes some more work. + +[C++ code](/code/19_vertex_buffer.cpp) / +[Vertex shader](/code/18_shader_vertexbuffer.vert) / +[Fragment shader](/code/18_shader_vertexbuffer.frag) diff --git a/en/04_Vertex_buffers/02_Staging_buffer.md b/en/04_Vertex_buffers/02_Staging_buffer.md new file mode 100644 index 00000000..289e74d4 --- /dev/null +++ b/en/04_Vertex_buffers/02_Staging_buffer.md @@ -0,0 +1,267 @@ +## Introduction + +The vertex buffer we have right now works correctly, but the memory type that +allows us to access it from the CPU may not be the most optimal memory type for +the graphics card itself to read from. The most optimal memory has the +`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` flag and is usually not accessible by the +CPU on dedicated graphics cards. In this chapter we're going to create two +vertex buffers. One *staging buffer* in CPU accessible memory to upload the data +from the vertex array to, and the final vertex buffer in device local memory. +We'll then use a buffer copy command to move the data from the staging buffer to +the actual vertex buffer. + +## Transfer queue + +The buffer copy command requires a queue family that supports transfer +operations, which is indicated using `VK_QUEUE_TRANSFER_BIT`. The good news is +that any queue family with `VK_QUEUE_GRAPHICS_BIT` or `VK_QUEUE_COMPUTE_BIT` +capabilities already implicitly support `VK_QUEUE_TRANSFER_BIT` operations. The +implementation is not required to explicitly list it in `queueFlags` in those +cases. + +If you like a challenge, then you can still try to use a different queue family +specifically for transfer operations. It will require you to make the following +modifications to your program: + +* Modify `QueueFamilyIndices` and `findQueueFamilies` to explicitly look for a +queue family with the `VK_QUEUE_TRANSFER_BIT` bit, but not the +`VK_QUEUE_GRAPHICS_BIT`. +* Modify `createLogicalDevice` to request a handle to the transfer queue +* Create a second command pool for command buffers that are submitted on the +transfer queue family +* Change the `sharingMode` of resources to be `VK_SHARING_MODE_CONCURRENT` and +specify both the graphics and transfer queue families +* Submit any transfer commands like `vkCmdCopyBuffer` (which we'll be using in +this chapter) to the transfer queue instead of the graphics queue + +It's a bit of work, but it'll teach you a lot about how resources are shared +between queue families. + +## Abstracting buffer creation + +Because we're going to create multiple buffers in this chapter, it's a good idea +to move buffer creation to a helper function. Create a new function +`createBuffer` and move the code in `createVertexBuffer` (except mapping) to it. + +```c++ +void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); +} +``` + +Make sure to add parameters for the buffer size, memory properties and usage so +that we can use this function to create many different types of buffers. The +last two parameters are output variables to write the handles to. + +You can now remove the buffer creation and memory allocation code from +`createVertexBuffer` and just call `createBuffer` instead: + +```c++ +void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexBufferMemory); + + void* data; + vkMapMemory(device, vertexBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, vertexBufferMemory); +} +``` + +Run your program to make sure that the vertex buffer still works properly. + +## Using a staging buffer + +We're now going to change `createVertexBuffer` to only use a host visible buffer +as temporary buffer and use a device local one as actual vertex buffer. + +```c++ +void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); +} +``` + +We're now using a new `stagingBuffer` with `stagingBufferMemory` for mapping and +copying the vertex data. In this chapter we're going to use two new buffer usage +flags: + +* `VK_BUFFER_USAGE_TRANSFER_SRC_BIT`: Buffer can be used as source in a memory +transfer operation. +* `VK_BUFFER_USAGE_TRANSFER_DST_BIT`: Buffer can be used as destination in a +memory transfer operation. + +The `vertexBuffer` is now allocated from a memory type that is device local, +which generally means that we're not able to use `vkMapMemory`. However, we can +copy data from the `stagingBuffer` to the `vertexBuffer`. We have to indicate +that we intend to do that by specifying the transfer source flag for the +`stagingBuffer` and the transfer destination flag for the `vertexBuffer`, along +with the vertex buffer usage flag. + +We're now going to write a function to copy the contents from one buffer to +another, called `copyBuffer`. + +```c++ +void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + +} +``` + +Memory transfer operations are executed using command buffers, just like drawing +commands. Therefore we must first allocate a temporary command buffer. You may +wish to create a separate command pool for these kinds of short-lived buffers, +because the implementation may be able to apply memory allocation optimizations. +You should use the `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` flag during command +pool generation in that case. + +```c++ +void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); +} +``` + +And immediately start recording the command buffer: + +```c++ +VkCommandBufferBeginInfo beginInfo{}; +beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; +beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + +vkBeginCommandBuffer(commandBuffer, &beginInfo); +``` + +We're only going to use the command buffer once and wait with returning from the function until the copy +operation has finished executing. It's good practice to tell the driver about +our intent using `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`. + +```c++ +VkBufferCopy copyRegion{}; +copyRegion.srcOffset = 0; // Optional +copyRegion.dstOffset = 0; // Optional +copyRegion.size = size; +vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); +``` + +Contents of buffers are transferred using the `vkCmdCopyBuffer` command. It +takes the source and destination buffers as arguments, and an array of regions +to copy. The regions are defined in `VkBufferCopy` structs and consist of a +source buffer offset, destination buffer offset and size. It is not possible to +specify `VK_WHOLE_SIZE` here, unlike the `vkMapMemory` command. + +```c++ +vkEndCommandBuffer(commandBuffer); +``` + +This command buffer only contains the copy command, so we can stop recording +right after that. Now execute the command buffer to complete the transfer: + +```c++ +VkSubmitInfo submitInfo{}; +submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; +submitInfo.commandBufferCount = 1; +submitInfo.pCommandBuffers = &commandBuffer; + +vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); +vkQueueWaitIdle(graphicsQueue); +``` + +Unlike the draw commands, there are no events we need to wait on this time. We +just want to execute the transfer on the buffers immediately. There are again +two possible ways to wait on this transfer to complete. We could use a fence and +wait with `vkWaitForFences`, or simply wait for the transfer queue to become +idle with `vkQueueWaitIdle`. A fence would allow you to schedule multiple +transfers simultaneously and wait for all of them complete, instead of executing +one at a time. That may give the driver more opportunities to optimize. + +```c++ +vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); +``` + +Don't forget to clean up the command buffer used for the transfer operation. + +We can now call `copyBuffer` from the `createVertexBuffer` function to move the +vertex data to the device local buffer: + +```c++ +createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + +copyBuffer(stagingBuffer, vertexBuffer, bufferSize); +``` + +After copying the data from the staging buffer to the device buffer, we should +clean it up: + +```c++ + ... + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); +} +``` + +Run your program to verify that you're seeing the familiar triangle again. The +improvement may not be visible right now, but its vertex data is now being +loaded from high performance memory. This will matter when we're going to start +rendering more complex geometry. + +## Conclusion + +It should be noted that in a real world application, you're not supposed to +actually call `vkAllocateMemory` for every individual buffer. The maximum number +of simultaneous memory allocations is limited by the `maxMemoryAllocationCount` +physical device limit, which may be as low as `4096` even on high end hardware +like an NVIDIA GTX 1080. The right way to allocate memory for a large number of +objects at the same time is to create a custom allocator that splits up a single +allocation among many different objects by using the `offset` parameters that +we've seen in many functions. + +You can either implement such an allocator yourself, or use the +[VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) +library provided by the GPUOpen initiative. However, for this tutorial it's okay +to use a separate allocation for every resource, because we won't come close to +hitting any of these limits for now. + +[C++ code](/code/20_staging_buffer.cpp) / +[Vertex shader](/code/18_shader_vertexbuffer.vert) / +[Fragment shader](/code/18_shader_vertexbuffer.frag) diff --git a/en/04_Vertex_buffers/03_Index_buffer.md b/en/04_Vertex_buffers/03_Index_buffer.md new file mode 100644 index 00000000..088263db --- /dev/null +++ b/en/04_Vertex_buffers/03_Index_buffer.md @@ -0,0 +1,179 @@ +## Introduction + +The 3D meshes you'll be rendering in a real world application will often share +vertices between multiple triangles. This already happens even with something +simple like drawing a rectangle: + +![](/images/vertex_vs_index.svg) + +Drawing a rectangle takes two triangles, which means that we need a vertex +buffer with 6 vertices. The problem is that the data of two vertices needs to be +duplicated resulting in 50% redundancy. It only gets worse with more complex +meshes, where vertices are reused in an average number of 3 triangles. The +solution to this problem is to use an *index buffer*. + +An index buffer is essentially an array of pointers into the vertex buffer. It +allows you to reorder the vertex data, and reuse existing data for multiple +vertices. The illustration above demonstrates what the index buffer would look +like for the rectangle if we have a vertex buffer containing each of the four +unique vertices. The first three indices define the upper-right triangle and the +last three indices define the vertices for the bottom-left triangle. + +## Index buffer creation + +In this chapter we're going to modify the vertex data and add index data to +draw a rectangle like the one in the illustration. Modify the vertex data to +represent the four corners: + +```c++ +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; +``` + +The top-left corner is red, top-right is green, bottom-right is blue and the +bottom-left is white. We'll add a new array `indices` to represent the contents +of the index buffer. It should match the indices in the illustration to draw the +upper-right triangle and bottom-left triangle. + +```c++ +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; +``` + +It is possible to use either `uint16_t` or `uint32_t` for your index buffer +depending on the number of entries in `vertices`. We can stick to `uint16_t` for +now because we're using less than 65535 unique vertices. + +Just like the vertex data, the indices need to be uploaded into a `VkBuffer` for +the GPU to be able to access them. Define two new class members to hold the +resources for the index buffer: + +```c++ +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; +VkBuffer indexBuffer; +VkDeviceMemory indexBufferMemory; +``` + +The `createIndexBuffer` function that we'll add now is almost identical to +`createVertexBuffer`: + +```c++ +void initVulkan() { + ... + createVertexBuffer(); + createIndexBuffer(); + ... +} + +void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); +} +``` + +There are only two notable differences. The `bufferSize` is now equal to the +number of indices times the size of the index type, either `uint16_t` or +`uint32_t`. The usage of the `indexBuffer` should be +`VK_BUFFER_USAGE_INDEX_BUFFER_BIT` instead of +`VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`, which makes sense. Other than that, the +process is exactly the same. We create a staging buffer to copy the contents of +`indices` to and then copy it to the final device local index buffer. + +The index buffer should be cleaned up at the end of the program, just like the +vertex buffer: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + ... +} +``` + +## Using an index buffer + +Using an index buffer for drawing involves two changes to +`recordCommandBuffer`. We first need to bind the index buffer, just like we did +for the vertex buffer. The difference is that you can only have a single index +buffer. It's unfortunately not possible to use different indices for each vertex +attribute, so we do still have to completely duplicate vertex data even if just +one attribute varies. + +```c++ +vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + +vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); +``` + +An index buffer is bound with `vkCmdBindIndexBuffer` which has the index buffer, +a byte offset into it, and the type of index data as parameters. As mentioned +before, the possible types are `VK_INDEX_TYPE_UINT16` and +`VK_INDEX_TYPE_UINT32`. + +Just binding an index buffer doesn't change anything yet, we also need to change +the drawing command to tell Vulkan to use the index buffer. Remove the +`vkCmdDraw` line and replace it with `vkCmdDrawIndexed`: + +```c++ +vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); +``` + +A call to this function is very similar to `vkCmdDraw`. The first two parameters +specify the number of indices and the number of instances. We're not using +instancing, so just specify `1` instance. The number of indices represents the +number of vertices that will be passed to the vertex shader. The next parameter +specifies an offset into the index buffer, using a value of `1` would cause the +graphics card to start reading at the second index. The second to last parameter +specifies an offset to add to the indices in the index buffer. The final +parameter specifies an offset for instancing, which we're not using. + +Now run your program and you should see the following: + +![](/images/indexed_rectangle.png) + +You now know how to save memory by reusing vertices with index buffers. This +will become especially important in a future chapter where we're going to load +complex 3D models. + +The previous chapter already mentioned that you should allocate multiple +resources like buffers from a single memory allocation, but in fact you should +go a step further. [Driver developers recommend](https://developer.nvidia.com/vulkan-memory-management) +that you also store multiple buffers, like the vertex and index buffer, into a +single `VkBuffer` and use offsets in commands like `vkCmdBindVertexBuffers`. The +advantage is that your data is more cache friendly in that case, because it's +closer together. It is even possible to reuse the same chunk of memory for +multiple resources if they are not used during the same render operations, +provided that their data is refreshed, of course. This is known as *aliasing* +and some Vulkan functions have explicit flags to specify that you want to do +this. + +[C++ code](/code/21_index_buffer.cpp) / +[Vertex shader](/code/18_shader_vertexbuffer.vert) / +[Fragment shader](/code/18_shader_vertexbuffer.frag) diff --git a/en/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.md b/en/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.md new file mode 100644 index 00000000..2bdcc2dc --- /dev/null +++ b/en/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.md @@ -0,0 +1,416 @@ +## Introduction + +We're now able to pass arbitrary attributes to the vertex shader for each +vertex, but what about global variables? We're going to move on to 3D graphics +from this chapter on and that requires a model-view-projection matrix. We could +include it as vertex data, but that's a waste of memory and it would require us +to update the vertex buffer whenever the transformation changes. The +transformation could easily change every single frame. + +The right way to tackle this in Vulkan is to use *resource descriptors*. A +descriptor is a way for shaders to freely access resources like buffers and +images. We're going to set up a buffer that contains the transformation matrices +and have the vertex shader access them through a descriptor. Usage of +descriptors consists of three parts: + +* Specify a descriptor set layout during pipeline creation +* Allocate a descriptor set from a descriptor pool +* Bind the descriptor set during rendering + +The *descriptor set layout* specifies the types of resources that are going to be +accessed by the pipeline, just like a render pass specifies the types of +attachments that will be accessed. A *descriptor set* specifies the actual +buffer or image resources that will be bound to the descriptors, just like a +framebuffer specifies the actual image views to bind to render pass attachments. +The descriptor set is then bound for the drawing commands just like the vertex +buffers and framebuffer. + +There are many types of descriptors, but in this chapter we'll work with uniform +buffer objects (UBO). We'll look at other types of descriptors in future +chapters, but the basic process is the same. Let's say we have the data we want +the vertex shader to have in a C struct like this: + +```c++ +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +``` + +Then we can copy the data to a `VkBuffer` and access it through a uniform buffer +object descriptor from the vertex shader like this: + +```glsl +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} +``` + +We're going to update the model, view and projection matrices every frame to +make the rectangle from the previous chapter spin around in 3D. + +## Vertex shader + +Modify the vertex shader to include the uniform buffer object like it was +specified above. I will assume that you are familiar with MVP transformations. +If you're not, see [the resource](https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/) +mentioned in the first chapter. + +```glsl +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} +``` + +Note that the order of the `uniform`, `in` and `out` declarations doesn't +matter. The `binding` directive is similar to the `location` directive for +attributes. We're going to reference this binding in the descriptor set layout. The +line with `gl_Position` is changed to use the transformations to compute the +final position in clip coordinates. Unlike the 2D triangles, the last component +of the clip coordinates may not be `1`, which will result in a division when +converted to the final normalized device coordinates on the screen. This is used +in perspective projection as the *perspective division* and is essential for +making closer objects look larger than objects that are further away. + +## Descriptor set layout + +The next step is to define the UBO on the C++ side and to tell Vulkan about this +descriptor in the vertex shader. + +```c++ +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +``` + +We can exactly match the definition in the shader using data types in GLM. The +data in the matrices is binary compatible with the way the shader expects it, so +we can later just `memcpy` a `UniformBufferObject` to a `VkBuffer`. + +We need to provide details about every descriptor binding used in the shaders +for pipeline creation, just like we had to do for every vertex attribute and its +`location` index. We'll set up a new function to define all of this information +called `createDescriptorSetLayout`. It should be called right before pipeline +creation, because we're going to need it there. + +```c++ +void initVulkan() { + ... + createDescriptorSetLayout(); + createGraphicsPipeline(); + ... +} + +... + +void createDescriptorSetLayout() { + +} +``` + +Every binding needs to be described through a `VkDescriptorSetLayoutBinding` +struct. + +```c++ +void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.descriptorCount = 1; +} +``` + +The first two fields specify the `binding` used in the shader and the type of +descriptor, which is a uniform buffer object. It is possible for the shader +variable to represent an array of uniform buffer objects, and `descriptorCount` +specifies the number of values in the array. This could be used to specify a +transformation for each of the bones in a skeleton for skeletal animation, for +example. Our MVP transformation is in a single uniform buffer object, so we're +using a `descriptorCount` of `1`. + +```c++ +uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; +``` + +We also need to specify in which shader stages the descriptor is going to be +referenced. The `stageFlags` field can be a combination of `VkShaderStageFlagBits` values +or the value `VK_SHADER_STAGE_ALL_GRAPHICS`. In our case, we're only referencing +the descriptor from the vertex shader. + +```c++ +uboLayoutBinding.pImmutableSamplers = nullptr; // Optional +``` + +The `pImmutableSamplers` field is only relevant for image sampling related +descriptors, which we'll look at later. You can leave this to its default value. + +All of the descriptor bindings are combined into a single +`VkDescriptorSetLayout` object. Define a new class member above +`pipelineLayout`: + +```c++ +VkDescriptorSetLayout descriptorSetLayout; +VkPipelineLayout pipelineLayout; +``` + +We can then create it using `vkCreateDescriptorSetLayout`. This function accepts +a simple `VkDescriptorSetLayoutCreateInfo` with the array of bindings: + +```c++ +VkDescriptorSetLayoutCreateInfo layoutInfo{}; +layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; +layoutInfo.bindingCount = 1; +layoutInfo.pBindings = &uboLayoutBinding; + +if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); +} +``` + +We need to specify the descriptor set layout during pipeline creation to tell +Vulkan which descriptors the shaders will be using. Descriptor set layouts are +specified in the pipeline layout object. Modify the `VkPipelineLayoutCreateInfo` +to reference the layout object: + +```c++ +VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; +pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +pipelineLayoutInfo.setLayoutCount = 1; +pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; +``` + +You may be wondering why it's possible to specify multiple descriptor set +layouts here, because a single one already includes all of the bindings. We'll +get back to that in the next chapter, where we'll look into descriptor pools and +descriptor sets. + +The descriptor set layout should stick around while we may create new graphics +pipelines i.e. until the program ends: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + ... +} +``` + +## Uniform buffer + +In the next chapter we'll specify the buffer that contains the UBO data for the +shader, but we need to create this buffer first. We're going to copy new data to +the uniform buffer every frame, so it doesn't really make any sense to have a +staging buffer. It would just add extra overhead in this case and likely degrade +performance instead of improving it. + +We should have multiple buffers, because multiple frames may be in flight at the same +time and we don't want to update the buffer in preparation of the next frame while a +previous one is still reading from it! Thus, we need to have as many uniform buffers +as we have frames in flight, and write to a uniform buffer that is not currently +being read by the GPU. + +To that end, add new class members for `uniformBuffers`, and `uniformBuffersMemory`: + +```c++ +VkBuffer indexBuffer; +VkDeviceMemory indexBufferMemory; + +std::vector uniformBuffers; +std::vector uniformBuffersMemory; +std::vector uniformBuffersMapped; +``` + +Similarly, create a new function `createUniformBuffers` that is called after +`createIndexBuffer` and allocates the buffers: + +```c++ +void initVulkan() { + ... + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + ... +} + +... + +void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + + vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]); + } +} +``` + +We map the buffer right after creation using `vkMapMemory` to get a pointer to which we can write the data later on. The buffer stays mapped to this pointer for the application's whole lifetime. This technique is called **"persistent mapping"** and works on all Vulkan implementations. Not having to map the buffer every time we need to update it increases performances, as mapping is not free. + +The uniform data will be used for all draw calls, so the buffer containing it should only be destroyed when we stop rendering. + +```c++ +void cleanup() { + ... + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + ... + +} +``` + +## Updating uniform data + +Create a new function `updateUniformBuffer` and add a call to it from the `drawFrame` function before submitting the next frame: + +```c++ +void drawFrame() { + ... + + updateUniformBuffer(currentFrame); + + ... + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + ... +} + +... + +void updateUniformBuffer(uint32_t currentImage) { + +} +``` + +This function will generate a new transformation every frame to make the +geometry spin around. We need to include two new headers to implement this +functionality: + +```c++ +#define GLM_FORCE_RADIANS +#include +#include + +#include +``` + +The `glm/gtc/matrix_transform.hpp` header exposes functions that can be used to +generate model transformations like `glm::rotate`, view transformations like +`glm::lookAt` and projection transformations like `glm::perspective`. The +`GLM_FORCE_RADIANS` definition is necessary to make sure that functions like +`glm::rotate` use radians as arguments, to avoid any possible confusion. + +The `chrono` standard library header exposes functions to do precise +timekeeping. We'll use this to make sure that the geometry rotates 90 degrees +per second regardless of frame rate. + +```c++ +void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); +} +``` + +The `updateUniformBuffer` function will start out with some logic to calculate +the time in seconds since rendering has started with floating point accuracy. + +We will now define the model, view and projection transformations in the +uniform buffer object. The model rotation will be a simple rotation around the +Z-axis using the `time` variable: + +```c++ +UniformBufferObject ubo{}; +ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); +``` + +The `glm::rotate` function takes an existing transformation, rotation angle and +rotation axis as parameters. The `glm::mat4(1.0f)` constructor returns an +identity matrix. Using a rotation angle of `time * glm::radians(90.0f)` +accomplishes the purpose of rotation 90 degrees per second. + +```c++ +ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); +``` + +For the view transformation I've decided to look at the geometry from above at a +45 degree angle. The `glm::lookAt` function takes the eye position, center +position and up axis as parameters. + +```c++ +ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); +``` + +I've chosen to use a perspective projection with a 45 degree vertical +field-of-view. The other parameters are the aspect ratio, near and far +view planes. It is important to use the current swap chain extent to calculate +the aspect ratio to take into account the new width and height of the window +after a resize. + +```c++ +ubo.proj[1][1] *= -1; +``` + +GLM was originally designed for OpenGL, where the Y coordinate of the clip +coordinates is inverted. The easiest way to compensate for that is to flip the +sign on the scaling factor of the Y axis in the projection matrix. If you don't +do this, then the image will be rendered upside down. + +All of the transformations are defined now, so we can copy the data in the +uniform buffer object to the current uniform buffer. This happens in exactly the same +way as we did for vertex buffers, except without a staging buffer. As noted earlier, we only map the uniform buffer once, so we can directly write to it without having to map again: + +```c++ +memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); +``` + +Using a UBO this way is not the most efficient way to pass frequently changing +values to the shader. A more efficient way to pass a small buffer of data to +shaders are *push constants*. We may look at these in a future chapter. + +In the next chapter we'll look at descriptor sets, which will actually bind the +`VkBuffer`s to the uniform buffer descriptors so that the shader can access this +transformation data. + +[C++ code](/code/22_descriptor_set_layout.cpp) / +[Vertex shader](/code/22_shader_ubo.vert) / +[Fragment shader](/code/22_shader_ubo.frag) diff --git a/en/05_Uniform_buffers/01_Descriptor_pool_and_sets.md b/en/05_Uniform_buffers/01_Descriptor_pool_and_sets.md new file mode 100644 index 00000000..b204db24 --- /dev/null +++ b/en/05_Uniform_buffers/01_Descriptor_pool_and_sets.md @@ -0,0 +1,391 @@ +## Introduction + +The descriptor set layout from the previous chapter describes the type of +descriptors that can be bound. In this chapter we're going to create +a descriptor set for each `VkBuffer` resource to bind it to the +uniform buffer descriptor. + +## Descriptor pool + +Descriptor sets can't be created directly, they must be allocated from a pool +like command buffers. The equivalent for descriptor sets is unsurprisingly +called a *descriptor pool*. We'll write a new function `createDescriptorPool` +to set it up. + +```c++ +void initVulkan() { + ... + createUniformBuffers(); + createDescriptorPool(); + ... +} + +... + +void createDescriptorPool() { + +} +``` + +We first need to describe which descriptor types our descriptor sets are going +to contain and how many of them, using `VkDescriptorPoolSize` structures. + +```c++ +VkDescriptorPoolSize poolSize{}; +poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +poolSize.descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); +``` + +We will allocate one of these descriptors for every frame. This +pool size structure is referenced by the main `VkDescriptorPoolCreateInfo`: + +```c++ +VkDescriptorPoolCreateInfo poolInfo{}; +poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; +poolInfo.poolSizeCount = 1; +poolInfo.pPoolSizes = &poolSize; +``` + +Aside from the maximum number of individual descriptors that are available, we +also need to specify the maximum number of descriptor sets that may be +allocated: + +```c++ +poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); +``` + +The structure has an optional flag similar to command pools that determines if +individual descriptor sets can be freed or not: +`VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT`. We're not going to touch +the descriptor set after creating it, so we don't need this flag. You can leave +`flags` to its default value of `0`. + +```c++ +VkDescriptorPool descriptorPool; + +... + +if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); +} +``` + +Add a new class member to store the handle of the descriptor pool and call +`vkCreateDescriptorPool` to create it. + +## Descriptor set + +We can now allocate the descriptor sets themselves. Add a `createDescriptorSets` +function for that purpose: + +```c++ +void initVulkan() { + ... + createDescriptorPool(); + createDescriptorSets(); + ... +} + +... + +void createDescriptorSets() { + +} +``` + +A descriptor set allocation is described with a `VkDescriptorSetAllocateInfo` +struct. You need to specify the descriptor pool to allocate from, the number of +descriptor sets to allocate, and the descriptor set layout to base them on: + +```c++ +std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); +VkDescriptorSetAllocateInfo allocInfo{}; +allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; +allocInfo.descriptorPool = descriptorPool; +allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); +allocInfo.pSetLayouts = layouts.data(); +``` + +In our case we will create one descriptor set for each frame in flight, all with the same layout. +Unfortunately we do need all the copies of the layout because the next function expects an array matching the number of sets. + +Add a class member to hold the descriptor set handles and allocate them with +`vkAllocateDescriptorSets`: + +```c++ +VkDescriptorPool descriptorPool; +std::vector descriptorSets; + +... + +descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); +if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); +} +``` + +You don't need to explicitly clean up descriptor sets, because they will be +automatically freed when the descriptor pool is destroyed. The call to +`vkAllocateDescriptorSets` will allocate descriptor sets, each with one uniform +buffer descriptor. + +```c++ +void cleanup() { + ... + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + ... +} +``` + +The descriptor sets have been allocated now, but the descriptors within still need +to be configured. We'll now add a loop to populate every descriptor: + +```c++ +for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + +} +``` + +Descriptors that refer to buffers, like our uniform buffer +descriptor, are configured with a `VkDescriptorBufferInfo` struct. This +structure specifies the buffer and the region within it that contains the data +for the descriptor. + +```c++ +for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); +} +``` + +If you're overwriting the whole buffer, like we are in this case, then it is also possible to use the `VK_WHOLE_SIZE` value for the range. The configuration of descriptors is updated using the `vkUpdateDescriptorSets` +function, which takes an array of `VkWriteDescriptorSet` structs as parameter. + +```c++ +VkWriteDescriptorSet descriptorWrite{}; +descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +descriptorWrite.dstSet = descriptorSets[i]; +descriptorWrite.dstBinding = 0; +descriptorWrite.dstArrayElement = 0; +``` + +The first two fields specify the descriptor set to update and the binding. We +gave our uniform buffer binding index `0`. Remember that descriptors can be +arrays, so we also need to specify the first index in the array that we want to +update. We're not using an array, so the index is simply `0`. + +```c++ +descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +descriptorWrite.descriptorCount = 1; +``` + +We need to specify the type of descriptor again. It's possible to update +multiple descriptors at once in an array, starting at index `dstArrayElement`. +The `descriptorCount` field specifies how many array elements you want to +update. + +```c++ +descriptorWrite.pBufferInfo = &bufferInfo; +descriptorWrite.pImageInfo = nullptr; // Optional +descriptorWrite.pTexelBufferView = nullptr; // Optional +``` + +The last field references an array with `descriptorCount` structs that actually +configure the descriptors. It depends on the type of descriptor which one of the +three you actually need to use. The `pBufferInfo` field is used for descriptors +that refer to buffer data, `pImageInfo` is used for descriptors that refer to +image data, and `pTexelBufferView` is used for descriptors that refer to buffer +views. Our descriptor is based on buffers, so we're using `pBufferInfo`. + +```c++ +vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); +``` + +The updates are applied using `vkUpdateDescriptorSets`. It accepts two kinds of +arrays as parameters: an array of `VkWriteDescriptorSet` and an array of +`VkCopyDescriptorSet`. The latter can be used to copy descriptors to each other, +as its name implies. + +## Using descriptor sets + +We now need to update the `recordCommandBuffer` function to actually bind the +right descriptor set for each frame to the descriptors in the shader with `vkCmdBindDescriptorSets`. This needs to be done before the `vkCmdDrawIndexed` call: + +```c++ +vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); +vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); +``` + +Unlike vertex and index buffers, descriptor sets are not unique to graphics +pipelines. Therefore we need to specify if we want to bind descriptor sets to +the graphics or compute pipeline. The next parameter is the layout that the +descriptors are based on. The next three parameters specify the index of the +first descriptor set, the number of sets to bind, and the array of sets to bind. +We'll get back to this in a moment. The last two parameters specify an array of +offsets that are used for dynamic descriptors. We'll look at these in a future +chapter. + +If you run your program now, then you'll notice that unfortunately nothing is +visible. The problem is that because of the Y-flip we did in the projection +matrix, the vertices are now being drawn in counter-clockwise order instead of +clockwise order. This causes backface culling to kick in and prevents +any geometry from being drawn. Go to the `createGraphicsPipeline` function and +modify the `frontFace` in `VkPipelineRasterizationStateCreateInfo` to correct +this: + +```c++ +rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; +rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; +``` + +Run your program again and you should now see the following: + +![](/images/spinning_quad.png) + +The rectangle has changed into a square because the projection matrix now +corrects for aspect ratio. The `updateUniformBuffer` takes care of screen +resizing, so we don't need to recreate the descriptor set in +`recreateSwapChain`. + +## Alignment requirements + +One thing we've glossed over so far is how exactly the data in the C++ structure should match with the uniform definition in the shader. It seems obvious enough to simply use the same types in both: + +```c++ +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; +``` + +However, that's not all there is to it. For example, try modifying the struct and shader to look like this: + +```c++ +struct UniformBufferObject { + glm::vec2 foo; + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +layout(binding = 0) uniform UniformBufferObject { + vec2 foo; + mat4 model; + mat4 view; + mat4 proj; +} ubo; +``` + +Recompile your shader and your program and run it and you'll find that the colorful square you worked so far has disappeared! That's because we haven't taken into account the *alignment requirements*. + +Vulkan expects the data in your structure to be aligned in memory in a specific way, for example: + +* Scalars have to be aligned by N (= 4 bytes given 32 bit floats). +* A `vec2` must be aligned by 2N (= 8 bytes) +* A `vec3` or `vec4` must be aligned by 4N (= 16 bytes) +* A nested structure must be aligned by the base alignment of its members rounded up to a multiple of 16. +* A `mat4` matrix must have the same alignment as a `vec4`. + +You can find the full list of alignment requirements in [the specification](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap15.html#interfaces-resources-layout). + +Our original shader with just three `mat4` fields already met the alignment requirements. As each `mat4` is 4 x 4 x 4 = 64 bytes in size, `model` has an offset of `0`, `view` has an offset of 64 and `proj` has an offset of 128. All of these are multiples of 16 and that's why it worked fine. + +The new structure starts with a `vec2` which is only 8 bytes in size and therefore throws off all of the offsets. Now `model` has an offset of `8`, `view` an offset of `72` and `proj` an offset of `136`, none of which are multiples of 16. To fix this problem we can use the [`alignas`](https://en.cppreference.com/w/cpp/language/alignas) specifier introduced in C++11: + +```c++ +struct UniformBufferObject { + glm::vec2 foo; + alignas(16) glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +``` + +If you now compile and run your program again you should see that the shader correctly receives its matrix values once again. + +Luckily there is a way to not have to think about these alignment requirements *most* of the time. We can define `GLM_FORCE_DEFAULT_ALIGNED_GENTYPES` right before including GLM: + +```c++ +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES +#include +``` + +This will force GLM to use a version of `vec2` and `mat4` that has the alignment requirements already specified for us. If you add this definition then you can remove the `alignas` specifier and your program should still work. + +Unfortunately this method can break down if you start using nested structures. Consider the following definition in the C++ code: + +```c++ +struct Foo { + glm::vec2 v; +}; + +struct UniformBufferObject { + Foo f1; + Foo f2; +}; +``` + +And the following shader definition: + +```c++ +struct Foo { + vec2 v; +}; + +layout(binding = 0) uniform UniformBufferObject { + Foo f1; + Foo f2; +} ubo; +``` + +In this case `f2` will have an offset of `8` whereas it should have an offset of `16` since it is a nested structure. In this case you must specify the alignment yourself: + +```c++ +struct UniformBufferObject { + Foo f1; + alignas(16) Foo f2; +}; +``` + +These gotchas are a good reason to always be explicit about alignment. That way you won't be caught offguard by the strange symptoms of alignment errors. + +```c++ +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; +``` + +Don't forget to recompile your shader after removing the `foo` field. + +## Multiple descriptor sets + +As some of the structures and function calls hinted at, it is actually possible +to bind multiple descriptor sets simultaneously. You need to specify a descriptor set layout for +each descriptor set when creating the pipeline layout. Shaders can then +reference specific descriptor sets like this: + +```c++ +layout(set = 0, binding = 0) uniform UniformBufferObject { ... } +``` + +You can use this feature to put descriptors that vary per-object and descriptors +that are shared into separate descriptor sets. In that case you avoid rebinding +most of the descriptors across draw calls which is potentially more efficient. + +[C++ code](/code/23_descriptor_sets.cpp) / +[Vertex shader](/code/22_shader_ubo.vert) / +[Fragment shader](/code/22_shader_ubo.frag) diff --git a/en/06_Texture_mapping/00_Images.md b/en/06_Texture_mapping/00_Images.md new file mode 100644 index 00000000..8c9967f6 --- /dev/null +++ b/en/06_Texture_mapping/00_Images.md @@ -0,0 +1,769 @@ +## Introduction + +The geometry has been colored using per-vertex colors so far, which is a rather +limited approach. In this part of the tutorial we're going to implement texture +mapping to make the geometry look more interesting. This will also allow us to +load and draw basic 3D models in a future chapter. + +Adding a texture to our application will involve the following steps: + +* Create an image object backed by device memory +* Fill it with pixels from an image file +* Create an image sampler +* Add a combined image sampler descriptor to sample colors from the texture + +We've already worked with image objects before, but those were automatically +created by the swap chain extension. This time we'll have to create one by +ourselves. Creating an image and filling it with data is similar to vertex +buffer creation. We'll start by creating a staging resource and filling it with +pixel data and then we copy this to the final image object that we'll use for +rendering. Although it is possible to create a staging image for this purpose, +Vulkan also allows you to copy pixels from a `VkBuffer` to an image and the API +for this is actually [faster on some hardware](https://developer.nvidia.com/vulkan-memory-management). +We'll first create this buffer and fill it with pixel values, and then we'll +create an image to copy the pixels to. Creating an image is not very different +from creating buffers. It involves querying the memory requirements, allocating +device memory and binding it, just like we've seen before. + +However, there is something extra that we'll have to take care of when working +with images. Images can have different *layouts* that affect how the pixels are +organized in memory. Due to the way graphics hardware works, simply storing the +pixels row by row may not lead to the best performance, for example. When +performing any operation on images, you must make sure that they have the layout +that is optimal for use in that operation. We've actually already seen some of +these layouts when we specified the render pass: + +* `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`: Optimal for presentation +* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`: Optimal as attachment for writing +colors from the fragment shader +* `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`: Optimal as source in a transfer +operation, like `vkCmdCopyImageToBuffer` +* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`: Optimal as destination in a transfer +operation, like `vkCmdCopyBufferToImage` +* `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`: Optimal for sampling from a shader + +One of the most common ways to transition the layout of an image is a *pipeline +barrier*. Pipeline barriers are primarily used for synchronizing access to +resources, like making sure that an image was written to before it is read, but +they can also be used to transition layouts. In this chapter we'll see how +pipeline barriers are used for this purpose. Barriers can additionally be used +to transfer queue family ownership when using `VK_SHARING_MODE_EXCLUSIVE`. + +## Image library + +There are many libraries available for loading images, and you can even write +your own code to load simple formats like BMP and PPM. In this tutorial we'll be +using the stb_image library from the [stb collection](https://github.com/nothings/stb). +The advantage of it is that all of the code is in a single file, so it doesn't +require any tricky build configuration. Download `stb_image.h` and store it in a +convenient location, like the directory where you saved GLFW and GLM. Add the +location to your include path. + +**Visual Studio** + +Add the directory with `stb_image.h` in it to the `Additional Include +Directories` paths. + +![](/images/include_dirs_stb.png) + +**Makefile** + +Add the directory with `stb_image.h` to the include directories for GCC: + +```text +VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64 +STB_INCLUDE_PATH = /home/user/libraries/stb + +... + +CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) +``` + +## Loading an image + +Include the image library like this: + +```c++ +#define STB_IMAGE_IMPLEMENTATION +#include +``` + +The header only defines the prototypes of the functions by default. One code +file needs to include the header with the `STB_IMAGE_IMPLEMENTATION` definition +to include the function bodies, otherwise we'll get linking errors. + +```c++ +void initVulkan() { + ... + createCommandPool(); + createTextureImage(); + createVertexBuffer(); + ... +} + +... + +void createTextureImage() { + +} +``` + +Create a new function `createTextureImage` where we'll load an image and upload +it into a Vulkan image object. We're going to use command buffers, so it should +be called after `createCommandPool`. + +Create a new directory `textures` next to the `shaders` directory to store +texture images in. We're going to load an image called `texture.jpg` from that +directory. I've chosen to use the following +[CC0 licensed image](https://pixabay.com/en/statue-sculpture-fig-historically-1275469/) +resized to 512 x 512 pixels, but feel free to pick any image you want. The +library supports most common image file formats, like JPEG, PNG, BMP and GIF. + +![](/images/texture.jpg) + +Loading an image with this library is really easy: + +```c++ +void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } +} +``` + +The `stbi_load` function takes the file path and number of channels to load as +arguments. The `STBI_rgb_alpha` value forces the image to be loaded with an +alpha channel, even if it doesn't have one, which is nice for consistency with +other textures in the future. The middle three parameters are outputs for the +width, height and actual number of channels in the image. The pointer that is +returned is the first element in an array of pixel values. The pixels are laid +out row by row with 4 bytes per pixel in the case of `STBI_rgb_alpha` for a +total of `texWidth * texHeight * 4` values. + +## Staging buffer + +We're now going to create a buffer in host visible memory so that we can use +`vkMapMemory` and copy the pixels to it. Add variables for this temporary buffer +to the `createTextureImage` function: + +```c++ +VkBuffer stagingBuffer; +VkDeviceMemory stagingBufferMemory; +``` + +The buffer should be in host visible memory so that we can map it and it should +be usable as a transfer source so that we can copy it to an image later on: + +```c++ +createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); +``` + +We can then directly copy the pixel values that we got from the image loading +library to the buffer: + +```c++ +void* data; +vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); +vkUnmapMemory(device, stagingBufferMemory); +``` + +Don't forget to clean up the original pixel array now: + +```c++ +stbi_image_free(pixels); +``` + +## Texture Image + +Although we could set up the shader to access the pixel values in the buffer, +it's better to use image objects in Vulkan for this purpose. Image objects will +make it easier and faster to retrieve colors by allowing us to use 2D +coordinates, for one. Pixels within an image object are known as texels and +we'll use that name from this point on. Add the following new class members: + +```c++ +VkImage textureImage; +VkDeviceMemory textureImageMemory; +``` + +The parameters for an image are specified in a `VkImageCreateInfo` struct: + +```c++ +VkImageCreateInfo imageInfo{}; +imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; +imageInfo.imageType = VK_IMAGE_TYPE_2D; +imageInfo.extent.width = static_cast(texWidth); +imageInfo.extent.height = static_cast(texHeight); +imageInfo.extent.depth = 1; +imageInfo.mipLevels = 1; +imageInfo.arrayLayers = 1; +``` + +The image type, specified in the `imageType` field, tells Vulkan with what kind +of coordinate system the texels in the image are going to be addressed. It is +possible to create 1D, 2D and 3D images. One dimensional images can be used to +store an array of data or gradient, two dimensional images are mainly used for +textures, and three dimensional images can be used to store voxel volumes, for +example. The `extent` field specifies the dimensions of the image, basically how +many texels there are on each axis. That's why `depth` must be `1` instead of +`0`. Our texture will not be an array and we won't be using mipmapping for now. + +```c++ +imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB; +``` + +Vulkan supports many possible image formats, but we should use the same format +for the texels as the pixels in the buffer, otherwise the copy operation will +fail. + +```c++ +imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; +``` + +The `tiling` field can have one of two values: + +* `VK_IMAGE_TILING_LINEAR`: Texels are laid out in row-major order like our +`pixels` array +* `VK_IMAGE_TILING_OPTIMAL`: Texels are laid out in an implementation defined +order for optimal access + +Unlike the layout of an image, the tiling mode cannot be changed at a later +time. If you want to be able to directly access texels in the memory of the +image, then you must use `VK_IMAGE_TILING_LINEAR`. We will be using a staging +buffer instead of a staging image, so this won't be necessary. We will be using +`VK_IMAGE_TILING_OPTIMAL` for efficient access from the shader. + +```c++ +imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +``` + +There are only two possible values for the `initialLayout` of an image: + +* `VK_IMAGE_LAYOUT_UNDEFINED`: Not usable by the GPU and the very first +transition will discard the texels. +* `VK_IMAGE_LAYOUT_PREINITIALIZED`: Not usable by the GPU, but the first +transition will preserve the texels. + +There are few situations where it is necessary for the texels to be preserved +during the first transition. One example, however, would be if you wanted to use +an image as a staging image in combination with the `VK_IMAGE_TILING_LINEAR` +layout. In that case, you'd want to upload the texel data to it and then +transition the image to be a transfer source without losing the data. In our +case, however, we're first going to transition the image to be a transfer +destination and then copy texel data to it from a buffer object, so we don't +need this property and can safely use `VK_IMAGE_LAYOUT_UNDEFINED`. + +```c++ +imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; +``` + +The `usage` field has the same semantics as the one during buffer creation. The +image is going to be used as destination for the buffer copy, so it should be +set up as a transfer destination. We also want to be able to access the image +from the shader to color our mesh, so the usage should include +`VK_IMAGE_USAGE_SAMPLED_BIT`. + +```c++ +imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; +``` + +The image will only be used by one queue family: the one that supports graphics +(and therefore also) transfer operations. + +```c++ +imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; +imageInfo.flags = 0; // Optional +``` + +The `samples` flag is related to multisampling. This is only relevant for images +that will be used as attachments, so stick to one sample. There are some +optional flags for images that are related to sparse images. Sparse images are +images where only certain regions are actually backed by memory. If you were +using a 3D texture for a voxel terrain, for example, then you could use this to +avoid allocating memory to store large volumes of "air" values. We won't be +using it in this tutorial, so leave it to its default value of `0`. + +```c++ +if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); +} +``` + +The image is created using `vkCreateImage`, which doesn't have any particularly +noteworthy parameters. It is possible that the `VK_FORMAT_R8G8B8A8_SRGB` format +is not supported by the graphics hardware. You should have a list of acceptable +alternatives and go with the best one that is supported. However, support for +this particular format is so widespread that we'll skip this step. Using +different formats would also require annoying conversions. We will get back to +this in the depth buffer chapter, where we'll implement such a system. + +```c++ +VkMemoryRequirements memRequirements; +vkGetImageMemoryRequirements(device, textureImage, &memRequirements); + +VkMemoryAllocateInfo allocInfo{}; +allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; +allocInfo.allocationSize = memRequirements.size; +allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + +if (vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); +} + +vkBindImageMemory(device, textureImage, textureImageMemory, 0); +``` + +Allocating memory for an image works in exactly the same way as allocating +memory for a buffer. Use `vkGetImageMemoryRequirements` instead of +`vkGetBufferMemoryRequirements`, and use `vkBindImageMemory` instead of +`vkBindBufferMemory`. + +This function is already getting quite large and there'll be a need to create +more images in later chapters, so we should abstract image creation into a +`createImage` function, like we did for buffers. Create the function and move +the image object creation and memory allocation to it: + +```c++ +void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); +} +``` + +I've made the width, height, format, tiling mode, usage, and memory properties +parameters, because these will all vary between the images we'll be creating +throughout this tutorial. + +The `createTextureImage` function can now be simplified to: + +```c++ +void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +} +``` + +## Layout transitions + +The function we're going to write now involves recording and executing a command +buffer again, so now's a good time to move that logic into a helper function or +two: + +```c++ +VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; +} + +void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); +} +``` + +The code for these functions is based on the existing code in `copyBuffer`. You +can now simplify that function to: + +```c++ +void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); +} +``` + +If we were still using buffers, then we could now write a function to record and +execute `vkCmdCopyBufferToImage` to finish the job, but this command requires +the image to be in the right layout first. Create a new function to handle +layout transitions: + +```c++ +void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + endSingleTimeCommands(commandBuffer); +} +``` + +One of the most common ways to perform layout transitions is using an *image +memory barrier*. A pipeline barrier like that is generally used to synchronize +access to resources, like ensuring that a write to a buffer completes before +reading from it, but it can also be used to transition image layouts and +transfer queue family ownership when `VK_SHARING_MODE_EXCLUSIVE` is used. There +is an equivalent *buffer memory barrier* to do this for buffers. + +```c++ +VkImageMemoryBarrier barrier{}; +barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; +barrier.oldLayout = oldLayout; +barrier.newLayout = newLayout; +``` + +The first two fields specify layout transition. It is possible to use +`VK_IMAGE_LAYOUT_UNDEFINED` as `oldLayout` if you don't care about the existing +contents of the image. + +```c++ +barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; +barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; +``` + +If you are using the barrier to transfer queue family ownership, then these two +fields should be the indices of the queue families. They must be set to +`VK_QUEUE_FAMILY_IGNORED` if you don't want to do this (not the default value!). + +```c++ +barrier.image = image; +barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +barrier.subresourceRange.baseMipLevel = 0; +barrier.subresourceRange.levelCount = 1; +barrier.subresourceRange.baseArrayLayer = 0; +barrier.subresourceRange.layerCount = 1; +``` + +The `image` and `subresourceRange` specify the image that is affected and the +specific part of the image. Our image is not an array and does not have mipmapping +levels, so only one level and layer are specified. + +```c++ +barrier.srcAccessMask = 0; // TODO +barrier.dstAccessMask = 0; // TODO +``` + +Barriers are primarily used for synchronization purposes, so you must specify +which types of operations that involve the resource must happen before the +barrier, and which operations that involve the resource must wait on the +barrier. We need to do that despite already using `vkQueueWaitIdle` to manually +synchronize. The right values depend on the old and new layout, so we'll get +back to this once we've figured out which transitions we're going to use. + +```c++ +vkCmdPipelineBarrier( + commandBuffer, + 0 /* TODO */, 0 /* TODO */, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier +); +``` + +All types of pipeline barriers are submitted using the same function. The first +parameter after the command buffer specifies in which pipeline stage the +operations occur that should happen before the barrier. The second parameter +specifies the pipeline stage in which operations will wait on the barrier. The +pipeline stages that you are allowed to specify before and after the barrier +depend on how you use the resource before and after the barrier. The allowed +values are listed in [this table](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-access-types-supported) +of the specification. For example, if you're going to read from a uniform after +the barrier, you would specify a usage of `VK_ACCESS_UNIFORM_READ_BIT` and the +earliest shader that will read from the uniform as pipeline stage, for example +`VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT`. It would not make sense to specify +a non-shader pipeline stage for this type of usage and the validation layers +will warn you when you specify a pipeline stage that does not match the type of +usage. + +The third parameter is either `0` or `VK_DEPENDENCY_BY_REGION_BIT`. The latter +turns the barrier into a per-region condition. That means that the +implementation is allowed to already begin reading from the parts of a resource +that were written so far, for example. + +The last three pairs of parameters reference arrays of pipeline barriers of the +three available types: memory barriers, buffer memory barriers, and image memory +barriers like the one we're using here. Note that we're not using the `VkFormat` +parameter yet, but we'll be using that one for special transitions in the depth +buffer chapter. + +## Copying buffer to image + +Before we get back to `createTextureImage`, we're going to write one more helper +function: `copyBufferToImage`: + +```c++ +void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + endSingleTimeCommands(commandBuffer); +} +``` + +Just like with buffer copies, you need to specify which part of the buffer is +going to be copied to which part of the image. This happens through +`VkBufferImageCopy` structs: + +```c++ +VkBufferImageCopy region{}; +region.bufferOffset = 0; +region.bufferRowLength = 0; +region.bufferImageHeight = 0; + +region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +region.imageSubresource.mipLevel = 0; +region.imageSubresource.baseArrayLayer = 0; +region.imageSubresource.layerCount = 1; + +region.imageOffset = {0, 0, 0}; +region.imageExtent = { + width, + height, + 1 +}; +``` + +Most of these fields are self-explanatory. The `bufferOffset` specifies the byte +offset in the buffer at which the pixel values start. The `bufferRowLength` and +`bufferImageHeight` fields specify how the pixels are laid out in memory. For +example, you could have some padding bytes between rows of the image. Specifying +`0` for both indicates that the pixels are simply tightly packed like they are +in our case. The `imageSubresource`, `imageOffset` and `imageExtent` fields +indicate to which part of the image we want to copy the pixels. + +Buffer to image copy operations are enqueued using the `vkCmdCopyBufferToImage` +function: + +```c++ +vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion +); +``` + +The fourth parameter indicates which layout the image is currently using. I'm +assuming here that the image has already been transitioned to the layout that is +optimal for copying pixels to. Right now we're only copying one chunk of pixels +to the whole image, but it's possible to specify an array of `VkBufferImageCopy` +to perform many different copies from this buffer to the image in one operation. + +## Preparing the texture image + +We now have all of the tools we need to finish setting up the texture image, so +we're going back to the `createTextureImage` function. The last thing we did +there was creating the texture image. The next step is to copy the staging +buffer to the texture image. This involves two steps: + +* Transition the texture image to `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` +* Execute the buffer to image copy operation + +This is easy to do with the functions we just created: + +```c++ +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); +copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +``` + +The image was created with the `VK_IMAGE_LAYOUT_UNDEFINED` layout, so that one +should be specified as old layout when transitioning `textureImage`. Remember +that we can do this because we don't care about its contents before performing +the copy operation. + +To be able to start sampling from the texture image in the shader, we need one +last transition to prepare it for shader access: + +```c++ +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); +``` + +## Transition barrier masks + +If you run your application with validation layers enabled now, then you'll see that +it complains about the access masks and pipeline stages in +`transitionImageLayout` being invalid. We still need to set those based on the +layouts in the transition. + +There are two transitions we need to handle: + +* Undefined → transfer destination: transfer writes that don't need to wait on +anything +* Transfer destination → shader reading: shader reads should wait on transfer +writes, specifically the shader reads in the fragment shader, because that's +where we're going to use the texture + +These rules are specified using the following access masks and pipeline stages: + +```c++ +VkPipelineStageFlags sourceStage; +VkPipelineStageFlags destinationStage; + +if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; +} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; +} else { + throw std::invalid_argument("unsupported layout transition!"); +} + +vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier +); +``` + +As you can see in the aforementioned table, transfer writes must occur in the +pipeline transfer stage. Since the writes don't have to wait on anything, you +may specify an empty access mask and the earliest possible pipeline stage +`VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` for the pre-barrier operations. It should be +noted that `VK_PIPELINE_STAGE_TRANSFER_BIT` is not a *real* stage within the +graphics and compute pipelines. It is more of a pseudo-stage where transfers +happen. See [the documentation](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#VkPipelineStageFlagBits) +for more information and other examples of pseudo-stages. + +The image will be written in the same pipeline stage and subsequently read by +the fragment shader, which is why we specify shader reading access in the +fragment shader pipeline stage. + +If we need to do more transitions in the future, then we'll extend the function. +The application should now run successfully, although there are of course no +visual changes yet. + +One thing to note is that command buffer submission results in implicit +`VK_ACCESS_HOST_WRITE_BIT` synchronization at the beginning. Since the +`transitionImageLayout` function executes a command buffer with only a single +command, you could use this implicit synchronization and set `srcAccessMask` to +`0` if you ever needed a `VK_ACCESS_HOST_WRITE_BIT` dependency in a layout +transition. It's up to you if you want to be explicit about it or not, but I'm +personally not a fan of relying on these OpenGL-like "hidden" operations. + +There is actually a special type of image layout that supports all operations, +`VK_IMAGE_LAYOUT_GENERAL`. The problem with it, of course, is that it doesn't +necessarily offer the best performance for any operation. It is required for +some special cases, like using an image as both input and output, or for reading +an image after it has left the preinitialized layout. + +All of the helper functions that submit commands so far have been set up to +execute synchronously by waiting for the queue to become idle. For practical +applications it is recommended to combine these operations in a single command +buffer and execute them asynchronously for higher throughput, especially the +transitions and copy in the `createTextureImage` function. Try to experiment +with this by creating a `setupCommandBuffer` that the helper functions record +commands into, and add a `flushSetupCommands` to execute the commands that have +been recorded so far. It's best to do this after the texture mapping works to +check if the texture resources are still set up correctly. + +## Cleanup + +Finish the `createTextureImage` function by cleaning up the staging buffer and +its memory at the end: + +```c++ + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); +} +``` + +The main texture image is used until the end of the program: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + ... +} +``` + +The image now contains the texture, but we still need a way to access it from +the graphics pipeline. We'll work on that in the next chapter. + +[C++ code](/code/24_texture_image.cpp) / +[Vertex shader](/code/22_shader_ubo.vert) / +[Fragment shader](/code/22_shader_ubo.frag) diff --git a/en/06_Texture_mapping/01_Image_view_and_sampler.md b/en/06_Texture_mapping/01_Image_view_and_sampler.md new file mode 100644 index 00000000..9d98c9e4 --- /dev/null +++ b/en/06_Texture_mapping/01_Image_view_and_sampler.md @@ -0,0 +1,369 @@ +In this chapter we're going to create two more resources that are needed for the +graphics pipeline to sample an image. The first resource is one that we've +already seen before while working with the swap chain images, but the second one +is new - it relates to how the shader will read texels from the image. + +## Texture image view + +We've seen before, with the swap chain images and the framebuffer, that images +are accessed through image views rather than directly. We will also need to +create such an image view for the texture image. + +Add a class member to hold a `VkImageView` for the texture image and create a +new function `createTextureImageView` where we'll create it: + +```c++ +VkImageView textureImageView; + +... + +void initVulkan() { + ... + createTextureImage(); + createTextureImageView(); + createVertexBuffer(); + ... +} + +... + +void createTextureImageView() { + +} +``` + +The code for this function can be based directly on `createImageViews`. The only +two changes you have to make are the `format` and the `image`: + +```c++ +VkImageViewCreateInfo viewInfo{}; +viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; +viewInfo.image = textureImage; +viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; +viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB; +viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +viewInfo.subresourceRange.baseMipLevel = 0; +viewInfo.subresourceRange.levelCount = 1; +viewInfo.subresourceRange.baseArrayLayer = 0; +viewInfo.subresourceRange.layerCount = 1; +``` + +I've left out the explicit `viewInfo.components` initialization, because +`VK_COMPONENT_SWIZZLE_IDENTITY` is defined as `0` anyway. Finish creating the +image view by calling `vkCreateImageView`: + +```c++ +if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture image view!"); +} +``` + +Because so much of the logic is duplicated from `createImageViews`, you may wish +to abstract it into a new `createImageView` function: + +```c++ +VkImageView createImageView(VkImage image, VkFormat format) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; +} +``` + +The `createTextureImageView` function can now be simplified to: + +```c++ +void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB); +} +``` + +And `createImageViews` can be simplified to: + +```c++ +void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); + } +} +``` + +Make sure to destroy the image view at the end of the program, right before +destroying the image itself: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); +``` + +## Samplers + +It is possible for shaders to read texels directly from images, but that is not +very common when they are used as textures. Textures are usually accessed +through samplers, which will apply filtering and transformations to compute the +final color that is retrieved. + +These filters are helpful to deal with problems like oversampling. Consider a +texture that is mapped to geometry with more fragments than texels. If you +simply took the closest texel for the texture coordinate in each fragment, then +you would get a result like the first image: + +![](/images/texture_filtering.png) + +If you combined the 4 closest texels through linear interpolation, then you +would get a smoother result like the one on the right. Of course your +application may have art style requirements that fit the left style more (think +Minecraft), but the right is preferred in conventional graphics applications. A +sampler object automatically applies this filtering for you when reading a color +from the texture. + +Undersampling is the opposite problem, where you have more texels than +fragments. This will lead to artifacts when sampling high frequency patterns +like a checkerboard texture at a sharp angle: + +![](/images/anisotropic_filtering.png) + +As shown in the left image, the texture turns into a blurry mess in the +distance. The solution to this is [anisotropic filtering](https://en.wikipedia.org/wiki/Anisotropic_filtering), +which can also be applied automatically by a sampler. + +Aside from these filters, a sampler can also take care of transformations. It +determines what happens when you try to read texels outside the image through +its *addressing mode*. The image below displays some of the possibilities: + +![](/images/texture_addressing.png) + +We will now create a function `createTextureSampler` to set up such a sampler +object. We'll be using that sampler to read colors from the texture in the +shader later on. + +```c++ +void initVulkan() { + ... + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + ... +} + +... + +void createTextureSampler() { + +} +``` + +Samplers are configured through a `VkSamplerCreateInfo` structure, which +specifies all filters and transformations that it should apply. + +```c++ +VkSamplerCreateInfo samplerInfo{}; +samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; +samplerInfo.magFilter = VK_FILTER_LINEAR; +samplerInfo.minFilter = VK_FILTER_LINEAR; +``` + +The `magFilter` and `minFilter` fields specify how to interpolate texels that +are magnified or minified. Magnification concerns the oversampling problem +describes above, and minification concerns undersampling. The choices are +`VK_FILTER_NEAREST` and `VK_FILTER_LINEAR`, corresponding to the modes +demonstrated in the images above. + +```c++ +samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; +samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; +samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; +``` + +The addressing mode can be specified per axis using the `addressMode` fields. +The available values are listed below. Most of these are demonstrated in the +image above. Note that the axes are called U, V and W instead of X, Y and Z. +This is a convention for texture space coordinates. + +* `VK_SAMPLER_ADDRESS_MODE_REPEAT`: Repeat the texture when going beyond the +image dimensions. +* `VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT`: Like repeat, but inverts the +coordinates to mirror the image when going beyond the dimensions. +* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE`: Take the color of the edge closest to +the coordinate beyond the image dimensions. +* `VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE`: Like clamp to edge, but +instead uses the edge opposite to the closest edge. +* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER`: Return a solid color when sampling +beyond the dimensions of the image. + +It doesn't really matter which addressing mode we use here, because we're not +going to sample outside of the image in this tutorial. However, the repeat mode +is probably the most common mode, because it can be used to tile textures like +floors and walls. + +```c++ +samplerInfo.anisotropyEnable = VK_TRUE; +samplerInfo.maxAnisotropy = ???; +``` + +These two fields specify if anisotropic filtering should be used. There is no +reason not to use this unless performance is a concern. The `maxAnisotropy` +field limits the amount of texel samples that can be used to calculate the final +color. A lower value results in better performance, but lower quality results. +To figure out which value we can use, we need to retrieve the properties of the physical device like so: + +```c++ +VkPhysicalDeviceProperties properties{}; +vkGetPhysicalDeviceProperties(physicalDevice, &properties); +``` + +If you look at the documentation for the `VkPhysicalDeviceProperties` structure, you'll see that it contains a `VkPhysicalDeviceLimits` member named `limits`. This struct in turn has a member called `maxSamplerAnisotropy` and this is the maximum value we can specify for `maxAnisotropy`. If we want to go for maximum quality, we can simply use that value directly: + +```c++ +samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; +``` + +You can either query the properties at the beginning of your program and pass them around to the functions that need them, or query them in the `createTextureSampler` function itself. + +```c++ +samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; +``` + +The `borderColor` field specifies which color is returned when sampling beyond +the image with clamp to border addressing mode. It is possible to return black, +white or transparent in either float or int formats. You cannot specify an +arbitrary color. + +```c++ +samplerInfo.unnormalizedCoordinates = VK_FALSE; +``` + +The `unnormalizedCoordinates` field specifies which coordinate system you want +to use to address texels in an image. If this field is `VK_TRUE`, then you can +simply use coordinates within the `[0, texWidth)` and `[0, texHeight)` range. If +it is `VK_FALSE`, then the texels are addressed using the `[0, 1)` range on all +axes. Real-world applications almost always use normalized coordinates, because +then it's possible to use textures of varying resolutions with the exact same +coordinates. + +```c++ +samplerInfo.compareEnable = VK_FALSE; +samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; +``` + +If a comparison function is enabled, then texels will first be compared to a +value, and the result of that comparison is used in filtering operations. This +is mainly used for [percentage-closer filtering](https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch11.html) +on shadow maps. We'll look at this in a future chapter. + +```c++ +samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; +samplerInfo.mipLodBias = 0.0f; +samplerInfo.minLod = 0.0f; +samplerInfo.maxLod = 0.0f; +``` + +All of these fields apply to mipmapping. We will look at mipmapping in a [later +chapter](/Generating_Mipmaps), but basically it's another type of filter that can be applied. + +The functioning of the sampler is now fully defined. Add a class member to +hold the handle of the sampler object and create the sampler with +`vkCreateSampler`: + +```c++ +VkImageView textureImageView; +VkSampler textureSampler; + +... + +void createTextureSampler() { + ... + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } +} +``` + +Note the sampler does not reference a `VkImage` anywhere. The sampler is a +distinct object that provides an interface to extract colors from a texture. It +can be applied to any image you want, whether it is 1D, 2D or 3D. This is +different from many older APIs, which combined texture images and filtering into +a single state. + +Destroy the sampler at the end of the program when we'll no longer be accessing +the image: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + ... +} +``` + +## Anisotropy device feature + +If you run your program right now, you'll see a validation layer message like +this: + +![](/images/validation_layer_anisotropy.png) + +That's because anisotropic filtering is actually an optional device feature. We +need to update the `createLogicalDevice` function to request it: + +```c++ +VkPhysicalDeviceFeatures deviceFeatures{}; +deviceFeatures.samplerAnisotropy = VK_TRUE; +``` + +And even though it is very unlikely that a modern graphics card will not support +it, we should update `isDeviceSuitable` to check if it is available: + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + ... + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; +} +``` + +The `vkGetPhysicalDeviceFeatures` repurposes the `VkPhysicalDeviceFeatures` +struct to indicate which features are supported rather than requested by setting +the boolean values. + +Instead of enforcing the availability of anisotropic filtering, it's also +possible to simply not use it by conditionally setting: + +```c++ +samplerInfo.anisotropyEnable = VK_FALSE; +samplerInfo.maxAnisotropy = 1.0f; +``` + +In the next chapter we will expose the image and sampler objects to the shaders +to draw the texture onto the square. + +[C++ code](/code/25_sampler.cpp) / +[Vertex shader](/code/22_shader_ubo.vert) / +[Fragment shader](/code/22_shader_ubo.frag) diff --git a/en/06_Texture_mapping/02_Combined_image_sampler.md b/en/06_Texture_mapping/02_Combined_image_sampler.md new file mode 100644 index 00000000..0f1e5496 --- /dev/null +++ b/en/06_Texture_mapping/02_Combined_image_sampler.md @@ -0,0 +1,296 @@ +## Introduction + +We looked at descriptors for the first time in the uniform buffers part of the +tutorial. In this chapter we will look at a new type of descriptor: *combined +image sampler*. This descriptor makes it possible for shaders to access an image +resource through a sampler object like the one we created in the previous +chapter. + +We'll start by modifying the descriptor set layout, descriptor pool and descriptor +set to include such a combined image sampler descriptor. After that, we're going +to add texture coordinates to `Vertex` and modify the fragment shader to read +colors from the texture instead of just interpolating the vertex colors. + +## Updating the descriptors + +Browse to the `createDescriptorSetLayout` function and add a +`VkDescriptorSetLayoutBinding` for a combined image sampler descriptor. We'll +simply put it in the binding after the uniform buffer: + +```c++ +VkDescriptorSetLayoutBinding samplerLayoutBinding{}; +samplerLayoutBinding.binding = 1; +samplerLayoutBinding.descriptorCount = 1; +samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; +samplerLayoutBinding.pImmutableSamplers = nullptr; +samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + +std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; +VkDescriptorSetLayoutCreateInfo layoutInfo{}; +layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; +layoutInfo.bindingCount = static_cast(bindings.size()); +layoutInfo.pBindings = bindings.data(); +``` + +Make sure to set the `stageFlags` to indicate that we intend to use the combined +image sampler descriptor in the fragment shader. That's where the color of the +fragment is going to be determined. It is possible to use texture sampling in +the vertex shader, for example to dynamically deform a grid of vertices by a +[heightmap](https://en.wikipedia.org/wiki/Heightmap). + +We must also create a larger descriptor pool to make room for the allocation +of the combined image sampler by adding another `VkPoolSize` of type +`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER` to the +`VkDescriptorPoolCreateInfo`. Go to the `createDescriptorPool` function and +modify it to include a `VkDescriptorPoolSize` for this descriptor: + +```c++ +std::array poolSizes{}; +poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); +poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; +poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + +VkDescriptorPoolCreateInfo poolInfo{}; +poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; +poolInfo.poolSizeCount = static_cast(poolSizes.size()); +poolInfo.pPoolSizes = poolSizes.data(); +poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); +``` + +Inadequate descriptor pools are a good example of a problem that the validation +layers will not catch: As of Vulkan 1.1, `vkAllocateDescriptorSets` may fail +with the error code `VK_ERROR_POOL_OUT_OF_MEMORY` if the pool is not +sufficiently large, but the driver may also try to solve the problem internally. +This means that sometimes (depending on hardware, pool size and allocation size) +the driver will let us get away with an allocation that exceeds the limits of +our descriptor pool. Other times, `vkAllocateDescriptorSets` will fail and +return `VK_ERROR_POOL_OUT_OF_MEMORY`. This can be particularly frustrating if +the allocation succeeds on some machines, but fails on others. + +Since Vulkan shifts the responsiblity for the allocation to the driver, it is no +longer a strict requirement to only allocate as many descriptors of a certain +type (`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`, etc.) as specified by the +corresponding `descriptorCount` members for the creation of the descriptor pool. +However, it remains best practise to do so, and in the future, +`VK_LAYER_KHRONOS_validation` will warn about this type of problem if you enable +[Best Practice Validation](https://vulkan.lunarg.com/doc/view/1.4.304.0/linux/best_practices.html). + +The final step is to bind the actual image and sampler resources to the +descriptors in the descriptor set. Go to the `createDescriptorSets` function. + +```c++ +for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo{}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + ... +} +``` + +The resources for a combined image sampler structure must be specified in a +`VkDescriptorImageInfo` struct, just like the buffer resource for a uniform +buffer descriptor is specified in a `VkDescriptorBufferInfo` struct. This is +where the objects from the previous chapter come together. + +```c++ +std::array descriptorWrites{}; + +descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +descriptorWrites[0].dstSet = descriptorSets[i]; +descriptorWrites[0].dstBinding = 0; +descriptorWrites[0].dstArrayElement = 0; +descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +descriptorWrites[0].descriptorCount = 1; +descriptorWrites[0].pBufferInfo = &bufferInfo; + +descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +descriptorWrites[1].dstSet = descriptorSets[i]; +descriptorWrites[1].dstBinding = 1; +descriptorWrites[1].dstArrayElement = 0; +descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; +descriptorWrites[1].descriptorCount = 1; +descriptorWrites[1].pImageInfo = &imageInfo; + +vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); +``` + +The descriptors must be updated with this image info, just like the buffer. This +time we're using the `pImageInfo` array instead of `pBufferInfo`. The descriptors +are now ready to be used by the shaders! + +## Texture coordinates + +There is one important ingredient for texture mapping that is still missing, and +that's the actual texture coordinates for each vertex. The texture coordinates determine how the +image is actually mapped to the geometry. + +```c++ +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } +}; +``` + +Modify the `Vertex` struct to include a `vec2` for texture coordinates. Make +sure to also add a `VkVertexInputAttributeDescription` so that we can use access +texture coordinates as input in the vertex shader. That is necessary to be able +to pass them to the fragment shader for interpolation across the surface of the +square. + +```c++ +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}} +}; +``` + +In this tutorial, I will simply fill the square with the texture by using +coordinates from `0, 0` in the top-left corner to `1, 1` in the bottom-right +corner. Feel free to experiment with different coordinates. Try using +coordinates below `0` or above `1` to see the addressing modes in action! + +## Shaders + +The final step is modifying the shaders to sample colors from the texture. We +first need to modify the vertex shader to pass through the texture coordinates +to the fragment shader: + +```glsl +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} +``` + +Just like the per vertex colors, the `fragTexCoord` values will be smoothly +interpolated across the area of the square by the rasterizer. We can visualize +this by having the fragment shader output the texture coordinates as colors: + +```glsl +#version 450 + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragTexCoord, 0.0, 1.0); +} +``` + +You should see something like the image below. Don't forget to recompile the +shaders! + +![](/images/texcoord_visualization.png) + +The green channel represents the horizontal coordinates and the red channel the +vertical coordinates. The black and yellow corners confirm that the texture +coordinates are correctly interpolated from `0, 0` to `1, 1` across the square. +Visualizing data using colors is the shader programming equivalent of `printf` +debugging, for lack of a better option! + +A combined image sampler descriptor is represented in GLSL by a sampler uniform. +Add a reference to it in the fragment shader: + +```glsl +layout(binding = 1) uniform sampler2D texSampler; +``` + +There are equivalent `sampler1D` and `sampler3D` types for other types of +images. Make sure to use the correct binding here. + +```glsl +void main() { + outColor = texture(texSampler, fragTexCoord); +} +``` + +Textures are sampled using the built-in `texture` function. It takes a `sampler` +and coordinate as arguments. The sampler automatically takes care of the +filtering and transformations in the background. You should now see the texture +on the square when you run the application: + +![](/images/texture_on_square.png) + +Try experimenting with the addressing modes by scaling the texture coordinates +to values higher than `1`. For example, the following fragment shader produces +the result in the image below when using `VK_SAMPLER_ADDRESS_MODE_REPEAT`: + +```glsl +void main() { + outColor = texture(texSampler, fragTexCoord * 2.0); +} +``` + +![](/images/texture_on_square_repeated.png) + +You can also manipulate the texture colors using the vertex colors: + +```glsl +void main() { + outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0); +} +``` + +I've separated the RGB and alpha channels here to not scale the alpha channel. + +![](/images/texture_on_square_colorized.png) + +You now know how to access images in shaders! This is a very powerful technique +when combined with images that are also written to in framebuffers. You can use +these images as inputs to implement cool effects like post-processing and camera +displays within the 3D world. + +[C++ code](/code/26_texture_mapping.cpp) / +[Vertex shader](/code/26_shader_textures.vert) / +[Fragment shader](/code/26_shader_textures.frag) diff --git a/en/07_Depth_buffering.md b/en/07_Depth_buffering.md new file mode 100644 index 00000000..92040cb3 --- /dev/null +++ b/en/07_Depth_buffering.md @@ -0,0 +1,600 @@ +## Introduction + +The geometry we've worked with so far is projected into 3D, but it's still +completely flat. In this chapter we're going to add a Z coordinate to the +position to prepare for 3D meshes. We'll use this third coordinate to place a +square over the current square to see a problem that arises when geometry is not +sorted by depth. + +## 3D geometry + +Change the `Vertex` struct to use a 3D vector for the position, and update the +`format` in the corresponding `VkVertexInputAttributeDescription`: + +```c++ +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + ... + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + ... + } +}; +``` + +Next, update the vertex shader to accept and transform 3D coordinates as input. +Don't forget to recompile it afterwards! + +```glsl +layout(location = 0) in vec3 inPosition; + +... + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} +``` + +Lastly, update the `vertices` container to include Z coordinates: + +```c++ +const std::vector vertices = { + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} +}; +``` + +If you run your application now, then you should see exactly the same result as +before. It's time to add some extra geometry to make the scene more interesting, +and to demonstrate the problem that we're going to tackle in this chapter. +Duplicate the vertices to define positions for a square right under the current +one like this: + +![](/images/extra_square.svg) + +Use Z coordinates of `-0.5f` and add the appropriate indices for the extra +square: + +```c++ +const std::vector vertices = { + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + + {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4 +}; +``` + +Run your program now and you'll see something resembling an Escher illustration: + +![](/images/depth_issues.png) + +The problem is that the fragments of the lower square are drawn over the +fragments of the upper square, simply because it comes later in the index array. +There are two ways to solve this: + +* Sort all of the draw calls by depth from back to front +* Use depth testing with a depth buffer + +The first approach is commonly used for drawing transparent objects, because +order-independent transparency is a difficult challenge to solve. However, the +problem of ordering fragments by depth is much more commonly solved using a +*depth buffer*. A depth buffer is an additional attachment that stores the depth +for every position, just like the color attachment stores the color of every +position. Every time the rasterizer produces a fragment, the depth test will +check if the new fragment is closer than the previous one. If it isn't, then the +new fragment is discarded. A fragment that passes the depth test writes its own +depth to the depth buffer. It is possible to manipulate this value from the +fragment shader, just like you can manipulate the color output. + +```c++ +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +``` + +The perspective projection matrix generated by GLM will use the OpenGL depth +range of `-1.0` to `1.0` by default. We need to configure it to use the Vulkan +range of `0.0` to `1.0` using the `GLM_FORCE_DEPTH_ZERO_TO_ONE` definition. + +## Depth image and view + +A depth attachment is based on an image, just like the color attachment. The +difference is that the swap chain will not automatically create depth images for us. We only need a single depth image, because only one draw operation is +running at once. The depth image will again require the trifecta of resources: +image, memory and image view. + +```c++ +VkImage depthImage; +VkDeviceMemory depthImageMemory; +VkImageView depthImageView; +``` + +Create a new function `createDepthResources` to set up these resources: + +```c++ +void initVulkan() { + ... + createCommandPool(); + createDepthResources(); + createTextureImage(); + ... +} + +... + +void createDepthResources() { + +} +``` + +Creating a depth image is fairly straightforward. It should have the same +resolution as the color attachment, defined by the swap chain extent, an image +usage appropriate for a depth attachment, optimal tiling and device local +memory. The only question is: what is the right format for a depth image? The +format must contain a depth component, indicated by `_D??_` in the `VK_FORMAT_`. + +Unlike the texture image, we don't necessarily need a specific format, because +we won't be directly accessing the texels from the program. It just needs to +have a reasonable accuracy, at least 24 bits is common in real-world +applications. There are several formats that fit this requirement: + +* `VK_FORMAT_D32_SFLOAT`: 32-bit float for depth +* `VK_FORMAT_D32_SFLOAT_S8_UINT`: 32-bit signed float for depth and 8 bit +stencil component +* `VK_FORMAT_D24_UNORM_S8_UINT`: 24-bit float for depth and 8 bit stencil +component + +The stencil component is used for [stencil tests](https://en.wikipedia.org/wiki/Stencil_buffer), +which is an additional test that can be combined with depth testing. We'll look +at this in a future chapter. + +We could simply go for the `VK_FORMAT_D32_SFLOAT` format, because support for it +is extremely common (see the hardware database), but it's nice to add some extra +flexibility to our application where possible. We're going to write a function +`findSupportedFormat` that takes a list of candidate formats in order from most +desirable to least desirable, and checks which is the first one that is +supported: + +```c++ +VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + +} +``` + +The support of a format depends on the tiling mode and usage, so we must also +include these as parameters. The support of a format can be queried using +the `vkGetPhysicalDeviceFormatProperties` function: + +```c++ +for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); +} +``` + +The `VkFormatProperties` struct contains three fields: + +* `linearTilingFeatures`: Use cases that are supported with linear tiling +* `optimalTilingFeatures`: Use cases that are supported with optimal tiling +* `bufferFeatures`: Use cases that are supported for buffers + +Only the first two are relevant here, and the one we check depends on the +`tiling` parameter of the function: + +```c++ +if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; +} else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; +} +``` + +If none of the candidate formats support the desired usage, then we can either +return a special value or simply throw an exception: + +```c++ +VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); +} +``` + +We'll use this function now to create a `findDepthFormat` helper function to +select a format with a depth component that supports usage as depth attachment: + +```c++ +VkFormat findDepthFormat() { + return findSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); +} +``` + +Make sure to use the `VK_FORMAT_FEATURE_` flag instead of `VK_IMAGE_USAGE_` in +this case. All of these candidate formats contain a depth component, but the +latter two also contain a stencil component. We won't be using that yet, but we +do need to take that into account when performing layout transitions on images +with these formats. Add a simple helper function that tells us if the chosen +depth format contains a stencil component: + +```c++ +bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; +} +``` + +Call the function to find a depth format from `createDepthResources`: + +```c++ +VkFormat depthFormat = findDepthFormat(); +``` + +We now have all the required information to invoke our `createImage` and +`createImageView` helper functions: + +```c++ +createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); +depthImageView = createImageView(depthImage, depthFormat); +``` + +However, the `createImageView` function currently assumes that the subresource +is always the `VK_IMAGE_ASPECT_COLOR_BIT`, so we will need to turn that field +into a parameter: + +```c++ +VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { + ... + viewInfo.subresourceRange.aspectMask = aspectFlags; + ... +} +``` + +Update all calls to this function to use the right aspect: + +```c++ +swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); +... +depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); +... +textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); +``` + +That's it for creating the depth image. We don't need to map it or copy another +image to it, because we're going to clear it at the start of the render pass +like the color attachment. + +### Explicitly transitioning the depth image + +We don't need to explicitly transition the layout of the image to a depth +attachment because we'll take care of this in the render pass. However, for +completeness I'll still describe the process in this section. You may skip it if +you like. + +Make a call to `transitionImageLayout` at the end of the `createDepthResources` +function like so: + +```c++ +transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); +``` + +The undefined layout can be used as initial layout, because there are no +existing depth image contents that matter. We need to update some of the logic +in `transitionImageLayout` to use the right subresource aspect: + +```c++ +if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + + if (hasStencilComponent(format)) { + barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } +} else { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +} +``` + +Although we're not using the stencil component, we do need to include it in the +layout transitions of the depth image. + +Finally, add the correct access masks and pipeline stages: + +```c++ +if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; +} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; +} else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; +} else { + throw std::invalid_argument("unsupported layout transition!"); +} +``` + +The depth buffer will be read from to perform depth tests to see if a fragment +is visible, and will be written to when a new fragment is drawn. The reading +happens in the `VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT` stage and the +writing in the `VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT`. You should pick the +earliest pipeline stage that matches the specified operations, so that it is +ready for usage as depth attachment when it needs to be. + +## Render pass + +We're now going to modify `createRenderPass` to include a depth attachment. +First specify the `VkAttachmentDescription`: + +```c++ +VkAttachmentDescription depthAttachment{}; +depthAttachment.format = findDepthFormat(); +depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; +depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; +depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; +depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; +depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; +depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; +``` + +The `format` should be the same as the depth image itself. This time we don't +care about storing the depth data (`storeOp`), because it will not be used after +drawing has finished. This may allow the hardware to perform additional +optimizations. Just like the color buffer, we don't care about the previous +depth contents, so we can use `VK_IMAGE_LAYOUT_UNDEFINED` as `initialLayout`. + +```c++ +VkAttachmentReference depthAttachmentRef{}; +depthAttachmentRef.attachment = 1; +depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; +``` + +Add a reference to the attachment for the first (and only) subpass: + +```c++ +VkSubpassDescription subpass{}; +subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; +subpass.colorAttachmentCount = 1; +subpass.pColorAttachments = &colorAttachmentRef; +subpass.pDepthStencilAttachment = &depthAttachmentRef; +``` + +Unlike color attachments, a subpass can only use a single depth (+stencil) +attachment. It wouldn't really make any sense to do depth tests on multiple +buffers. + +```c++ +std::array attachments = {colorAttachment, depthAttachment}; +VkRenderPassCreateInfo renderPassInfo{}; +renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; +renderPassInfo.attachmentCount = static_cast(attachments.size()); +renderPassInfo.pAttachments = attachments.data(); +renderPassInfo.subpassCount = 1; +renderPassInfo.pSubpasses = &subpass; +renderPassInfo.dependencyCount = 1; +renderPassInfo.pDependencies = &dependency; +``` + +Next, update the `VkSubpassDependency` struct to refer to both +attachments. + +```c++ +dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; +dependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; +dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; +dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; +``` + +Finally, we need to extend our subpass dependencies to make sure that there is no conflict between the transitioning of the depth image and it being cleared as part of its load operation. The depth image is first accessed in the early fragment test pipeline stage and because we have a load operation that *clears*, we should specify the access mask for writes. + +## Framebuffer + +The next step is to modify the framebuffer creation to bind the depth image to +the depth attachment. Go to `createFramebuffers` and specify the depth image +view as second attachment: + +```c++ +std::array attachments = { + swapChainImageViews[i], + depthImageView +}; + +VkFramebufferCreateInfo framebufferInfo{}; +framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; +framebufferInfo.renderPass = renderPass; +framebufferInfo.attachmentCount = static_cast(attachments.size()); +framebufferInfo.pAttachments = attachments.data(); +framebufferInfo.width = swapChainExtent.width; +framebufferInfo.height = swapChainExtent.height; +framebufferInfo.layers = 1; +``` + +The color attachment differs for every swap chain image, but the same depth +image can be used by all of them because only a single subpass is running at the +same time due to our semaphores. + +You'll also need to move the call to `createFramebuffers` to make sure that it +is called after the depth image view has actually been created: + +```c++ +void initVulkan() { + ... + createDepthResources(); + createFramebuffers(); + ... +} +``` + +## Clear values + +Because we now have multiple attachments with `VK_ATTACHMENT_LOAD_OP_CLEAR`, we +also need to specify multiple clear values. Go to `recordCommandBuffer` and +create an array of `VkClearValue` structs: + +```c++ +std::array clearValues{}; +clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; +clearValues[1].depthStencil = {1.0f, 0}; + +renderPassInfo.clearValueCount = static_cast(clearValues.size()); +renderPassInfo.pClearValues = clearValues.data(); +``` + +The range of depths in the depth buffer is `0.0` to `1.0` in Vulkan, where `1.0` +lies at the far view plane and `0.0` at the near view plane. The initial value +at each point in the depth buffer should be the furthest possible depth, which +is `1.0`. + +Note that the order of `clearValues` should be identical to the order of your attachments. + +## Depth and stencil state + +The depth attachment is ready to be used now, but depth testing still needs to +be enabled in the graphics pipeline. It is configured through the +`VkPipelineDepthStencilStateCreateInfo` struct: + +```c++ +VkPipelineDepthStencilStateCreateInfo depthStencil{}; +depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; +depthStencil.depthTestEnable = VK_TRUE; +depthStencil.depthWriteEnable = VK_TRUE; +``` + +The `depthTestEnable` field specifies if the depth of new fragments should be +compared to the depth buffer to see if they should be discarded. The +`depthWriteEnable` field specifies if the new depth of fragments that pass the +depth test should actually be written to the depth buffer. + +```c++ +depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; +``` + +The `depthCompareOp` field specifies the comparison that is performed to keep or +discard fragments. We're sticking to the convention of lower depth = closer, so +the depth of new fragments should be *less*. + +```c++ +depthStencil.depthBoundsTestEnable = VK_FALSE; +depthStencil.minDepthBounds = 0.0f; // Optional +depthStencil.maxDepthBounds = 1.0f; // Optional +``` + +The `depthBoundsTestEnable`, `minDepthBounds` and `maxDepthBounds` fields are +used for the optional depth bound test. Basically, this allows you to only keep +fragments that fall within the specified depth range. We won't be using this +functionality. + +```c++ +depthStencil.stencilTestEnable = VK_FALSE; +depthStencil.front = {}; // Optional +depthStencil.back = {}; // Optional +``` + +The last three fields configure stencil buffer operations, which we also won't +be using in this tutorial. If you want to use these operations, then you will +have to make sure that the format of the depth/stencil image contains a stencil +component. + +```c++ +pipelineInfo.pDepthStencilState = &depthStencil; +``` + +Update the `VkGraphicsPipelineCreateInfo` struct to reference the depth stencil +state we just filled in. A depth stencil state must always be specified if the +render pass contains a depth stencil attachment. + +If you run your program now, then you should see that the fragments of the +geometry are now correctly ordered: + +![](/images/depth_correct.png) + +## Handling window resize + +The resolution of the depth buffer should change when the window is resized to +match the new color attachment resolution. Extend the `recreateSwapChain` +function to recreate the depth resources in that case: + +```c++ +void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createDepthResources(); + createFramebuffers(); +} +``` + +The cleanup operations should happen in the swap chain cleanup function: + +```c++ +void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + ... +} +``` + +Congratulations, your application is now finally ready to render arbitrary 3D +geometry and have it look right. We're going to try this out in the next chapter +by drawing a textured model! + +[C++ code](/code/27_depth_buffering.cpp) / +[Vertex shader](/code/27_shader_depth.vert) / +[Fragment shader](/code/27_shader_depth.frag) diff --git a/en/08_Loading_models.md b/en/08_Loading_models.md new file mode 100644 index 00000000..f3ae89c0 --- /dev/null +++ b/en/08_Loading_models.md @@ -0,0 +1,332 @@ +## Introduction + +Your program is now ready to render textured 3D meshes, but the current geometry +in the `vertices` and `indices` arrays is not very interesting yet. In this +chapter we're going to extend the program to load the vertices and indices from +an actual model file to make the graphics card actually do some work. + +Many graphics API tutorials have the reader write their own OBJ loader in a +chapter like this. The problem with this is that any remotely interesting 3D +application will soon require features that are not supported by this file +format, like skeletal animation. We *will* load mesh data from an OBJ model in +this chapter, but we'll focus more on integrating the mesh data with the program +itself rather than the details of loading it from a file. + +## Library + +We will use the [tinyobjloader](https://github.com/syoyo/tinyobjloader) library +to load vertices and faces from an OBJ file. It's fast and it's easy to +integrate because it's a single file library like stb_image. Go to the +repository linked above and download the `tiny_obj_loader.h` file to a folder in +your library directory. + +**Visual Studio** + +Add the directory with `tiny_obj_loader.h` in it to the `Additional Include +Directories` paths. + +![](/images/include_dirs_tinyobjloader.png) + +**Makefile** + +Add the directory with `tiny_obj_loader.h` to the include directories for GCC: + +```text +VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64 +STB_INCLUDE_PATH = /home/user/libraries/stb +TINYOBJ_INCLUDE_PATH = /home/user/libraries/tinyobjloader + +... + +CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) -I$(TINYOBJ_INCLUDE_PATH) +``` + +## Sample mesh + +In this chapter we won't be enabling lighting yet, so it helps to use a sample +model that has lighting baked into the texture. An easy way to find such models +is to look for 3D scans on [Sketchfab](https://sketchfab.com/). Many of the +models on that site are available in OBJ format with a permissive license. + +For this tutorial I've decided to go with the [Viking room](https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38) +model by [nigelgoh](https://sketchfab.com/nigelgoh) ([CC BY 4.0](https://web.archive.org/web/20200428202538/https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38)). I tweaked the size and orientation of the model to use it +as a drop in replacement for the current geometry: + +* [viking_room.obj](/resources/viking_room.obj) +* [viking_room.png](/resources/viking_room.png) + +Feel free to use your own model, but make sure that it only consists of one +material and that is has dimensions of about 1.5 x 1.5 x 1.5 units. If it is +larger than that, then you'll have to change the view matrix. Put the model file +in a new `models` directory next to `shaders` and `textures`, and put the +texture image in the `textures` directory. + +Put two new configuration variables in your program to define the model and +texture paths: + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +``` + +And update `createTextureImage` to use this path variable: + +```c++ +stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); +``` + +## Loading vertices and indices + +We're going to load the vertices and indices from the model file now, so you +should remove the global `vertices` and `indices` arrays now. Replace them with +non-const containers as class members: + +```c++ +std::vector vertices; +std::vector indices; +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; +``` + +You should change the type of the indices from `uint16_t` to `uint32_t`, because +there are going to be a lot more vertices than 65535. Remember to also change +the `vkCmdBindIndexBuffer` parameter: + +```c++ +vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); +``` + +The tinyobjloader library is included in the same way as STB libraries. Include +the `tiny_obj_loader.h` file and make sure to define +`TINYOBJLOADER_IMPLEMENTATION` in one source file to include the function +bodies and avoid linker errors: + +```c++ +#define TINYOBJLOADER_IMPLEMENTATION +#include +``` + +We're now going to write a `loadModel` function that uses this library to +populate the `vertices` and `indices` containers with the vertex data from the +mesh. It should be called somewhere before the vertex and index buffers are +created: + +```c++ +void initVulkan() { + ... + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + ... +} + +... + +void loadModel() { + +} +``` + +A model is loaded into the library's data structures by calling the +`tinyobj::LoadObj` function: + +```c++ +void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(err); + } +} +``` + +An OBJ file consists of positions, normals, texture coordinates and faces. Faces +consist of an arbitrary amount of vertices, where each vertex refers to a +position, normal and/or texture coordinate by index. This makes it possible to +not just reuse entire vertices, but also individual attributes. + +The `attrib` container holds all of the positions, normals and texture +coordinates in its `attrib.vertices`, `attrib.normals` and `attrib.texcoords` +vectors. The `shapes` container contains all of the separate objects and their +faces. Each face consists of an array of vertices, and each vertex contains the +indices of the position, normal and texture coordinate attributes. OBJ models +can also define a material and texture per face, but we will be ignoring those. + +The `err` string contains errors and the `warn` string contains warnings that occurred while loading the +file, like a missing material definition. Loading only really failed if the +`LoadObj` function returns `false`. As mentioned above, faces in OBJ files can +actually contain an arbitrary number of vertices, whereas our application can +only render triangles. Luckily the `LoadObj` has an optional parameter to +automatically triangulate such faces, which is enabled by default. + +We're going to combine all of the faces in the file into a single model, so just +iterate over all of the shapes: + +```c++ +for (const auto& shape : shapes) { + +} +``` + +The triangulation feature has already made sure that there are three vertices +per face, so we can now directly iterate over the vertices and dump them +straight into our `vertices` vector: + +```c++ +for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertices.push_back(vertex); + indices.push_back(indices.size()); + } +} +``` + +For simplicity, we will assume that every vertex is unique for now, hence the +simple auto-increment indices. The `index` variable is of type +`tinyobj::index_t`, which contains the `vertex_index`, `normal_index` and +`texcoord_index` members. We need to use these indices to look up the actual +vertex attributes in the `attrib` arrays: + +```c++ +vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] +}; + +vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + attrib.texcoords[2 * index.texcoord_index + 1] +}; + +vertex.color = {1.0f, 1.0f, 1.0f}; +``` + +Unfortunately the `attrib.vertices` array is an array of `float` values instead +of something like `glm::vec3`, so you need to multiply the index by `3`. +Similarly, there are two texture coordinate components per entry. The offsets of +`0`, `1` and `2` are used to access the X, Y and Z components, or the U and V +components in the case of texture coordinates. + +Run your program now with optimization enabled (e.g. `Release` mode in Visual +Studio and with the `-O3` compiler flag for GCC`). This is necessary, because +otherwise loading the model will be very slow. You should see something like the +following: + +![](/images/inverted_texture_coordinates.png) + +Great, the geometry looks correct, but what's going on with the texture? The OBJ format assumes a coordinate system where a vertical coordinate of `0` means the bottom of the image, however we've uploaded our image into Vulkan in a top to bottom orientation where `0` means the top of the image. Solve this by +flipping the vertical component of the texture coordinates: + +```c++ +vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] +}; +``` + +When you run your program again, you should now see the correct result: + +![](/images/drawing_model.png) + +All that hard work is finally beginning to pay off with a demo like this! + +>As the model rotates you may notice that the rear (backside of the walls) looks a bit funny. This is normal and is simply because the model is not really designed to be viewed from that side. + +## Vertex deduplication + +Unfortunately we're not really taking advantage of the index buffer yet. The +`vertices` vector contains a lot of duplicated vertex data, because many +vertices are included in multiple triangles. We should keep only the unique +vertices and use the index buffer to reuse them whenever they come up. A +straightforward way to implement this is to use a `map` or `unordered_map` to +keep track of the unique vertices and respective indices: + +```c++ +#include + +... + +std::unordered_map uniqueVertices{}; + +for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + ... + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } +} +``` + +Every time we read a vertex from the OBJ file, we check if we've already seen a +vertex with the exact same position and texture coordinates before. If not, we +add it to `vertices` and store its index in the `uniqueVertices` container. +After that we add the index of the new vertex to `indices`. If we've seen the +exact same vertex before, then we look up its index in `uniqueVertices` and +store that index in `indices`. + +The program will fail to compile right now, because using a user-defined type +like our `Vertex` struct as key in a hash table requires us to implement two +functions: equality test and hash calculation. The former is easy to implement +by overriding the `==` operator in the `Vertex` struct: + +```c++ +bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; +} +``` + +A hash function for `Vertex` is implemented by specifying a template +specialization for `std::hash`. Hash functions are a complex topic, but +[cppreference.com recommends](http://en.cppreference.com/w/cpp/utility/hash) the +following approach combining the fields of a struct to create a decent quality +hash function: + +```c++ +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ + (hash()(vertex.color) << 1)) >> 1) ^ + (hash()(vertex.texCoord) << 1); + } + }; +} +``` + +This code should be placed outside the `Vertex` struct. The hash functions for +the GLM types need to be included using the following header: + +```c++ +#define GLM_ENABLE_EXPERIMENTAL +#include +``` + +The hash functions are defined in the `gtx` folder, which means that it is +technically still an experimental extension to GLM. Therefore you need to define +`GLM_ENABLE_EXPERIMENTAL` to use it. It means that the API could change with a +new version of GLM in the future, but in practice the API is very stable. + +You should now be able to successfully compile and run your program. If you +check the size of `vertices`, then you'll see that it has shrunk down from +1,500,000 to 265,645! That means that each vertex is reused in an average number +of ~6 triangles. This definitely saves us a lot of GPU memory. + +[C++ code](/code/28_model_loading.cpp) / +[Vertex shader](/code/27_shader_depth.vert) / +[Fragment shader](/code/27_shader_depth.frag) diff --git a/en/09_Generating_Mipmaps.md b/en/09_Generating_Mipmaps.md new file mode 100644 index 00000000..e4033136 --- /dev/null +++ b/en/09_Generating_Mipmaps.md @@ -0,0 +1,354 @@ +## Introduction +Our program can now load and render 3D models. In this chapter, we will add one more feature, mipmap generation. Mipmaps are widely used in games and rendering software, and Vulkan gives us complete control over how they are created. + +Mipmaps are precalculated, downscaled versions of an image. Each new image is half the width and height of the previous one. Mipmaps are used as a form of *Level of Detail* or *LOD.* Objects that are far away from the camera will sample their textures from the smaller mip images. Using smaller images increases the rendering speed and avoids artifacts such as [Moiré patterns](https://en.wikipedia.org/wiki/Moir%C3%A9_pattern). An example of what mipmaps look like: + +![](/images/mipmaps_example.jpg) + +## Image creation + +In Vulkan, each of the mip images is stored in different *mip levels* of a `VkImage`. Mip level 0 is the original image, and the mip levels after level 0 are commonly referred to as the *mip chain.* + +The number of mip levels is specified when the `VkImage` is created. Up until now, we have always set this value to one. We need to calculate the number of mip levels from the dimensions of the image. First, add a class member to store this number: + +```c++ +... +uint32_t mipLevels; +VkImage textureImage; +... +``` + +The value for `mipLevels` can be found once we've loaded the texture in `createTextureImage`: + +```c++ +int texWidth, texHeight, texChannels; +stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); +... +mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + +``` + +This calculates the number of levels in the mip chain. The `max` function selects the largest dimension. The `log2` function calculates how many times that dimension can be divided by 2. The `floor` function handles cases where the largest dimension is not a power of 2. `1` is added so that the original image has a mip level. + +To use this value, we need to change the `createImage`, `createImageView`, and `transitionImageLayout` functions to allow us to specify the number of mip levels. Add a `mipLevels` parameter to the functions: + +```c++ +void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + ... + imageInfo.mipLevels = mipLevels; + ... +} +``` +```c++ +VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) { + ... + viewInfo.subresourceRange.levelCount = mipLevels; + ... +``` +```c++ +void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) { + ... + barrier.subresourceRange.levelCount = mipLevels; + ... +``` + +Update all calls to these functions to use the right values: + +```c++ +createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); +... +createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +``` +```c++ +swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); +... +depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1); +... +textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels); +``` +```c++ +transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1); +... +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); +``` + + + +## Generating Mipmaps + +Our texture image now has multiple mip levels, but the staging buffer can only be used to fill mip level 0. The other levels are still undefined. To fill these levels we need to generate the data from the single level that we have. We will use the `vkCmdBlitImage` command. This command performs copying, scaling, and filtering operations. We will call this multiple times to *blit* data to each level of our texture image. + +`vkCmdBlitImage` is considered a transfer operation, so we must inform Vulkan that we intend to use the texture image as both the source and destination of a transfer. Add `VK_IMAGE_USAGE_TRANSFER_SRC_BIT` to the texture image's usage flags in `createTextureImage`: + +```c++ +... +createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +... +``` + +Like other image operations, `vkCmdBlitImage` depends on the layout of the image it operates on. We could transition the entire image to `VK_IMAGE_LAYOUT_GENERAL`, but this will most likely be slow. For optimal performance, the source image should be in `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` and the destination image should be in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. Vulkan allows us to transition each mip level of an image independently. Each blit will only deal with two mip levels at a time, so we can transition each level into the optimal layout between blits commands. + +`transitionImageLayout` only performs layout transitions on the entire image, so we'll need to write a few more pipeline barrier commands. Remove the existing transition to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` in `createTextureImage`: + +```c++ +... +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +//transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps +... +``` + +This will leave each level of the texture image in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. Each level will be transitioned to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` after the blit command reading from it is finished. + +We're now going to write the function that generates the mipmaps: + +```c++ +void generateMipmaps(VkImage image, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.image = image; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + endSingleTimeCommands(commandBuffer); +} +``` + +We're going to make several transitions, so we'll reuse this `VkImageMemoryBarrier`. The fields set above will remain the same for all barriers. `subresourceRange.miplevel`, `oldLayout`, `newLayout`, `srcAccessMask`, and `dstAccessMask` will be changed for each transition. + +```c++ +int32_t mipWidth = texWidth; +int32_t mipHeight = texHeight; + +for (uint32_t i = 1; i < mipLevels; i++) { + +} +``` + +This loop will record each of the `VkCmdBlitImage` commands. Note that the loop variable starts at 1, not 0. + +```c++ +barrier.subresourceRange.baseMipLevel = i - 1; +barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; +barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; +barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; +barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + +vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); +``` + +First, we transition level `i - 1` to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`. This transition will wait for level `i - 1` to be filled, either from the previous blit command, or from `vkCmdCopyBufferToImage`. The current blit command will wait on this transition. + +```c++ +VkImageBlit blit{}; +blit.srcOffsets[0] = { 0, 0, 0 }; +blit.srcOffsets[1] = { mipWidth, mipHeight, 1 }; +blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +blit.srcSubresource.mipLevel = i - 1; +blit.srcSubresource.baseArrayLayer = 0; +blit.srcSubresource.layerCount = 1; +blit.dstOffsets[0] = { 0, 0, 0 }; +blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; +blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +blit.dstSubresource.mipLevel = i; +blit.dstSubresource.baseArrayLayer = 0; +blit.dstSubresource.layerCount = 1; +``` + +Next, we specify the regions that will be used in the blit operation. The source mip level is `i - 1` and the destination mip level is `i`. The two elements of the `srcOffsets` array determine the 3D region that data will be blitted from. `dstOffsets` determines the region that data will be blitted to. The X and Y dimensions of the `dstOffsets[1]` are divided by two since each mip level is half the size of the previous level. The Z dimension of `srcOffsets[1]` and `dstOffsets[1]` must be 1, since a 2D image has a depth of 1. + +```c++ +vkCmdBlitImage(commandBuffer, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blit, + VK_FILTER_LINEAR); +``` + +Now, we record the blit command. Note that `textureImage` is used for both the `srcImage` and `dstImage` parameter. This is because we're blitting between different levels of the same image. The source mip level was just transitioned to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` and the destination level is still in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` from `createTextureImage`. + +Beware if you are using a dedicated transfer queue (as suggested in [Vertex buffers](!en/Vertex_buffers/Staging_buffer)): `vkCmdBlitImage` must be submitted to a queue with graphics capability. + +The last parameter allows us to specify a `VkFilter` to use in the blit. We have the same filtering options here that we had when making the `VkSampler`. We use the `VK_FILTER_LINEAR` to enable interpolation. + +```c++ +barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; +barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; +barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + +vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); +``` + +This barrier transitions mip level `i - 1` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. This transition waits on the current blit command to finish. All sampling operations will wait on this transition to finish. + +```c++ + ... + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 1) mipHeight /= 2; +} +``` + +At the end of the loop, we divide the current mip dimensions by two. We check each dimension before the division to ensure that dimension never becomes 0. This handles cases where the image is not square, since one of the mip dimensions would reach 1 before the other dimension. When this happens, that dimension should remain 1 for all remaining levels. + +```c++ + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + endSingleTimeCommands(commandBuffer); +} +``` + +Before we end the command buffer, we insert one more pipeline barrier. This barrier transitions the last mip level from `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. This wasn't handled by the loop, since the last mip level is never blitted from. + +Finally, add the call to `generateMipmaps` in `createTextureImage`: + +```c++ +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +//transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps +... +generateMipmaps(textureImage, texWidth, texHeight, mipLevels); +``` + +Our texture image's mipmaps are now completely filled. + +## Linear filtering support + +It is very convenient to use a built-in function like `vkCmdBlitImage` to generate all the mip levels, but unfortunately it is not guaranteed to be supported on all platforms. It requires the texture image format we use to support linear filtering, which can be checked with the `vkGetPhysicalDeviceFormatProperties` function. We will add a check to the `generateMipmaps` function for this. + +First add an additional parameter that specifies the image format: + +```c++ +void createTextureImage() { + ... + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, mipLevels); +} + +void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + + ... +} +``` + +In the `generateMipmaps` function, use `vkGetPhysicalDeviceFormatProperties` to request the properties of the texture image format: + +```c++ +void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + + // Check if image format supports linear blitting + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties); + + ... +``` + +The `VkFormatProperties` struct has three fields named `linearTilingFeatures`, `optimalTilingFeatures` and `bufferFeatures` that each describe how the format can be used depending on the way it is used. We create a texture image with the optimal tiling format, so we need to check `optimalTilingFeatures`. Support for the linear filtering feature can be checked with the `VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT`: + +```c++ +if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { + throw std::runtime_error("texture image format does not support linear blitting!"); +} +``` + +There are two alternatives in this case. You could implement a function that searches common texture image formats for one that *does* support linear blitting, or you could implement the mipmap generation in software with a library like [stb_image_resize](https://github.com/nothings/stb/blob/master/stb_image_resize.h). Each mip level can then be loaded into the image in the same way that you loaded the original image. + +It should be noted that it is uncommon in practice to generate the mipmap levels at runtime anyway. Usually they are pregenerated and stored in the texture file alongside the base level to improve loading speed. Implementing resizing in software and loading multiple levels from a file is left as an exercise to the reader. + +## Sampler + +While the `VkImage` holds the mipmap data, `VkSampler` controls how that data is read while rendering. Vulkan allows us to specify `minLod`, `maxLod`, `mipLodBias`, and `mipmapMode` ("Lod" means "Level of Detail"). When a texture is sampled, the sampler selects a mip level according to the following pseudocode: + +```c++ +lod = getLodLevelFromScreenSize(); //smaller when the object is close, may be negative +lod = clamp(lod + mipLodBias, minLod, maxLod); + +level = clamp(floor(lod), 0, texture.mipLevels - 1); //clamped to the number of mip levels in the texture + +if (mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST) { + color = sample(level); +} else { + color = blend(sample(level), sample(level + 1)); +} +``` + +If `samplerInfo.mipmapMode` is `VK_SAMPLER_MIPMAP_MODE_NEAREST`, `lod` selects the mip level to sample from. If the mipmap mode is `VK_SAMPLER_MIPMAP_MODE_LINEAR`, `lod` is used to select two mip levels to be sampled. Those levels are sampled and the results are linearly blended. + +The sample operation is also affected by `lod`: + +```c++ +if (lod <= 0) { + color = readTexture(uv, magFilter); +} else { + color = readTexture(uv, minFilter); +} +``` + +If the object is close to the camera, `magFilter` is used as the filter. If the object is further from the camera, `minFilter` is used. Normally, `lod` is non-negative, and is only 0 when close the camera. `mipLodBias` lets us force Vulkan to use lower `lod` and `level` than it would normally use. + +To see the results of this chapter, we need to choose values for our `textureSampler`. We've already set the `minFilter` and `magFilter` to use `VK_FILTER_LINEAR`. We just need to choose values for `minLod`, `maxLod`, `mipLodBias`, and `mipmapMode`. + +```c++ +void createTextureSampler() { + ... + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.minLod = 0.0f; // Optional + samplerInfo.maxLod = VK_LOD_CLAMP_NONE; + samplerInfo.mipLodBias = 0.0f; // Optional + ... +} +``` + +To allow the full range of mip levels to be used, we set `minLod` to 0.0f, and `maxLod` to `VK_LOD_CLAMP_NONE`. This constant is equal to `1000.0f`, which means that all available mipmap levels in the texture will be sampled. We have no reason to change the `lod` value, so we set `mipLodBias` to 0.0f. + +Now run your program and you should see the following: + +![](/images/mipmaps.png) + +It's not a dramatic difference, since our scene is so simple. There are subtle differences if you look closely. + +![](/images/mipmaps_comparison.png) + +The most noticeable difference is the writing on the papers. With mipmaps, the writing has been smoothed. Without mipmaps, the writing has harsh edges and gaps from Moiré artifacts. + +You can play around with the sampler settings to see how they affect mipmapping. For example, by changing `minLod`, you can force the sampler to not use the lowest mip levels: + +```c++ +samplerInfo.minLod = static_cast(mipLevels / 2); +``` + +These settings will produce this image: + + +![](/images/highmipmaps.png) + +This is how higher mip levels will be used when objects are further away from the camera. + + +[C++ code](/code/29_mipmapping.cpp) / +[Vertex shader](/code/27_shader_depth.vert) / +[Fragment shader](/code/27_shader_depth.frag) diff --git a/en/10_Multisampling.md b/en/10_Multisampling.md new file mode 100644 index 00000000..70b27b51 --- /dev/null +++ b/en/10_Multisampling.md @@ -0,0 +1,298 @@ +## Introduction + +Our program can now load multiple levels of detail for textures which fixes artifacts when rendering objects far away from the viewer. The image is now a lot smoother, however on closer inspection you will notice jagged saw-like patterns along the edges of drawn geometric shapes. This is especially visible in one of our early programs when we rendered a quad: + +![](/images/texcoord_visualization.png) + +This undesired effect is called "aliasing" and it's a result of a limited numbers of pixels that are available for rendering. Since there are no displays out there with unlimited resolution, it will be always visible to some extent. There's a number of ways to fix this and in this chapter we'll focus on one of the more popular ones: [Multisample anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing) (MSAA). + +In ordinary rendering, the pixel color is determined based on a single sample point which in most cases is the center of the target pixel on screen. If part of the drawn line passes through a certain pixel but doesn't cover the sample point, that pixel will be left blank, leading to the jagged "staircase" effect. + +![](/images/aliasing.png) + +What MSAA does is it uses multiple sample points per pixel (hence the name) to determine its final color. As one might expect, more samples lead to better results, however it is also more computationally expensive. + +![](/images/antialiasing.png) + +In our implementation, we will focus on using the maximum available sample count. Depending on your application this may not always be the best approach and it might be better to use less samples for the sake of higher performance if the final result meets your quality demands. + + +## Getting available sample count + +Let's start off by determining how many samples our hardware can use. Most modern GPUs support at least 8 samples but this number is not guaranteed to be the same everywhere. We'll keep track of it by adding a new class member: + +```c++ +... +VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT; +... +``` + +By default we'll be using only one sample per pixel which is equivalent to no multisampling, in which case the final image will remain unchanged. The exact maximum number of samples can be extracted from `VkPhysicalDeviceProperties` associated with our selected physical device. We're using a depth buffer, so we have to take into account the sample count for both color and depth. The highest sample count that is supported by both (&) will be the maximum we can support. Add a function that will fetch this information for us: + +```c++ +VkSampleCountFlagBits getMaxUsableSampleCount() { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); + + VkSampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; } + if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; } + if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; } + if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; } + if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; } + if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; } + + return VK_SAMPLE_COUNT_1_BIT; +} +``` + +We will now use this function to set the `msaaSamples` variable during the physical device selection process. For this, we have to slightly modify the `pickPhysicalDevice` function: + +```c++ +void pickPhysicalDevice() { + ... + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + msaaSamples = getMaxUsableSampleCount(); + break; + } + } + ... +} +``` + +## Setting up a render target + +In MSAA, each pixel is sampled in an offscreen buffer which is then rendered to the screen. This new buffer is slightly different from regular images we've been rendering to - they have to be able to store more than one sample per pixel. Once a multisampled buffer is created, it has to be resolved to the default framebuffer (which stores only a single sample per pixel). This is why we have to create an additional render target and modify our current drawing process. We only need one render target since only one drawing operation is active at a time, just like with the depth buffer. Add the following class members: + +```c++ +... +VkImage colorImage; +VkDeviceMemory colorImageMemory; +VkImageView colorImageView; +... +``` + +This new image will have to store the desired number of samples per pixel, so we need to pass this number to `VkImageCreateInfo` during the image creation process. Modify the `createImage` function by adding a `numSamples` parameter: + +```c++ +void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCountFlagBits numSamples, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + ... + imageInfo.samples = numSamples; + ... +``` + +For now, update all calls to this function using `VK_SAMPLE_COUNT_1_BIT` - we will be replacing this with proper values as we progress with implementation: + +```c++ +createImage(swapChainExtent.width, swapChainExtent.height, 1, VK_SAMPLE_COUNT_1_BIT, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); +... +createImage(texWidth, texHeight, mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +``` + +We will now create a multisampled color buffer. Add a `createColorResources` function and note that we're using `msaaSamples` here as a function parameter to `createImage`. We're also using only one mip level, since this is enforced by the Vulkan specification in case of images with more than one sample per pixel. Also, this color buffer doesn't need mipmaps since it's not going to be used as a texture: + +```c++ +void createColorResources() { + VkFormat colorFormat = swapChainImageFormat; + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, colorImage, colorImageMemory); + colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); +} +``` + +For consistency, call the function right before `createDepthResources`: + +```c++ +void initVulkan() { + ... + createColorResources(); + createDepthResources(); + ... +} +``` + +Now that we have a multisampled color buffer in place it's time to take care of depth. Modify `createDepthResources` and update the number of samples used by the depth buffer: + +```c++ +void createDepthResources() { + ... + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); + ... +} +``` + +We have now created a couple of new Vulkan resources, so let's not forget to release them when necessary: + +```c++ +void cleanupSwapChain() { + vkDestroyImageView(device, colorImageView, nullptr); + vkDestroyImage(device, colorImage, nullptr); + vkFreeMemory(device, colorImageMemory, nullptr); + ... +} +``` + +And update the `recreateSwapChain` so that the new color image can be recreated in the correct resolution when the window is resized: + +```c++ +void recreateSwapChain() { + ... + createImageViews(); + createColorResources(); + createDepthResources(); + ... +} +``` + +We made it past the initial MSAA setup, now we need to start using this new resource in our graphics pipeline, framebuffer, render pass and see the results! + +## Adding new attachments + +Let's take care of the render pass first. Modify `createRenderPass` and update color and depth attachment creation info structs: + +```c++ +void createRenderPass() { + ... + colorAttachment.samples = msaaSamples; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + ... + depthAttachment.samples = msaaSamples; + ... +``` + +You'll notice that we have changed the finalLayout from `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` to `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`. That's because multisampled images cannot be presented directly. We first need to resolve them to a regular image. This requirement does not apply to the depth buffer, since it won't be presented at any point. Therefore we will have to add only one new attachment for color which is a so-called resolve attachment: + +```c++ + ... + VkAttachmentDescription colorAttachmentResolve{}; + colorAttachmentResolve.format = swapChainImageFormat; + colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + ... +``` + +The render pass now has to be instructed to resolve multisampled color image into regular attachment. Create a new attachment reference that will point to the color buffer which will serve as the resolve target: + +```c++ + ... + VkAttachmentReference colorAttachmentResolveRef{}; + colorAttachmentResolveRef.attachment = 2; + colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + ... +``` + +Set the `pResolveAttachments` subpass struct member to point to the newly created attachment reference. This is enough to let the render pass define a multisample resolve operation which will let us render the image to screen: + +``` + ... + subpass.pResolveAttachments = &colorAttachmentResolveRef; + ... +``` + +Since we're reusing the multisampled color image, it's necessary to update the `srcAccessMask` of the `VkSubpassDependency`. This update ensures that any write operations to the color attachment are completed before subsequent ones begin, thus preventing write-after-write hazards that can lead to unstable rendering results: + +```c++ + ... + dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + ... +``` + +Now update render pass info struct with the new color attachment: + +```c++ + ... + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; + ... +``` + +With the render pass in place, modify `createFramebuffers` and add the new image view to the list: + +```c++ +void createFramebuffers() { + ... + std::array attachments = { + colorImageView, + depthImageView, + swapChainImageViews[i] + }; + ... +} +``` + +Finally, tell the newly created pipeline to use more than one sample by modifying `createGraphicsPipeline`: + +```c++ +void createGraphicsPipeline() { + ... + multisampling.rasterizationSamples = msaaSamples; + ... +} +``` + +Now run your program and you should see the following: + +![](/images/multisampling.png) + +Just like with mipmapping, the difference may not be apparent straight away. On a closer look you'll notice that the edges are not as jagged anymore and the whole image seems a bit smoother compared to the original. + +![](/images/multisampling_comparison.png) + +The difference is more noticable when looking up close at one of the edges: + +![](/images/multisampling_comparison2.png) + +## Quality improvements + +There are certain limitations of our current MSAA implementation which may impact the quality of the output image in more detailed scenes. For example, we're currently not solving potential problems caused by shader aliasing, i.e. MSAA only smoothens out the edges of geometry but not the interior filling. This may lead to a situation when you get a smooth polygon rendered on screen but the applied texture will still look aliased if it contains high contrasting colors. One way to approach this problem is to enable [Sample Shading](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap27.html#primsrast-sampleshading) which will improve the image quality even further, though at an additional performance cost: + +```c++ + +void createLogicalDevice() { + ... + deviceFeatures.sampleRateShading = VK_TRUE; // enable sample shading feature for the device + ... +} + +void createGraphicsPipeline() { + ... + multisampling.sampleShadingEnable = VK_TRUE; // enable sample shading in the pipeline + multisampling.minSampleShading = .2f; // min fraction for sample shading; closer to one is smoother + ... +} +``` + +In this example we'll leave sample shading disabled but in certain scenarios the quality improvement may be noticeable: + +![](/images/sample_shading.png) + +## Conclusion + +It has taken a lot of work to get to this point, but now you finally have a good +base for a Vulkan program. The knowledge of the basic principles of Vulkan that +you now possess should be sufficient to start exploring more of the features, +like: + +* Push constants +* Instanced rendering +* Dynamic uniforms +* Separate images and sampler descriptors +* Pipeline cache +* Multi-threaded command buffer generation +* Multiple subpasses +* Compute shaders + +The current program can be extended in many ways, like adding Blinn-Phong +lighting, post-processing effects and shadow mapping. You should be able to +learn how these effects work from tutorials for other APIs, because despite +Vulkan's explicitness, many concepts still work the same. + +[C++ code](/code/30_multisampling.cpp) / +[Vertex shader](/code/27_shader_depth.vert) / +[Fragment shader](/code/27_shader_depth.frag) diff --git a/en/11_Compute_Shader.md b/en/11_Compute_Shader.md new file mode 100644 index 00000000..628f58e9 --- /dev/null +++ b/en/11_Compute_Shader.md @@ -0,0 +1,650 @@ +## Introduction + +In this bonus chapter we'll take a look at compute shaders. Up until now all previous chapters dealt with the traditional graphics part of the Vulkan pipeline. But unlike older APIs like OpenGL, compute shader support in Vulkan is mandatory. This means that you can use compute shaders on every Vulkan implementation available, no matter if it's a high-end desktop GPU or a low-powered embedded device. + +This opens up the world of general purpose computing on graphics processor units (GPGPU), no matter where your application is running. GPGPU means that you can do general computations on your GPU, something that has traditionally been a domain of CPUs. But with GPUs having become more and more powerful and more flexible, many workloads that would require the general purpose capabilities of a CPU can now be done on the GPU in realtime. + +A few examples of where the compute capabilities of a GPU can be used are image manipulation, visibility testing, post processing, advanced lighting calculations, animations, physics (e.g. for a particle system) and much more. And it's even possible to use compute for non-visual computational only work that does not require any graphics output, e.g. number crunching or AI related things. This is called "headless compute". + +## Advantages + +Doing computationally expensive calculations on the GPU has several advantages. The most obvious one is offloading work from the CPU. Another one is not requiring moving data between the CPU's main memory and the GPU's memory. All of the data can stay on the GPU without having to wait for slow transfers from main memory. + +Aside from these, GPUs are heavily parallelized with some of them having tens of thousands of small compute units. This often makes them a better fit for highly parallel workflows than a CPU with a few large compute units. + +## The Vulkan pipeline + +It's important to know that compute is completely separated from the graphics part of the pipeline. This is visible in the following block diagram of the Vulkan pipeline from the official specification: + +![](/images/vulkan_pipeline_block_diagram.png) + +In this diagram we can see the traditional graphics part of the pipeline on the left, and several stages on the right that are not part of this graphics pipeline, including the compute shader (stage). With the compute shader stage being detached from the graphics pipeline we'll be able to use it anywhere we see fit. This is very different from e.g. the fragment shader which is always applied to the transformed output of the vertex shader. + +The center of the diagram also shows that e.g. descriptor sets are also used by compute, so everything we learned about descriptors layouts, descriptor sets and descriptors also applies here. + +## An example + +An easy to understand example that we will implement in this chapter is a GPU based particle system. Such systems are used in many games and often consist of thousands of particles that need to be updated at interactive frame rates. Rendering such a system requires 2 main components: vertices, passed as vertex buffers, and a way to update them based on some equation. + +The "classical" CPU based particle system would store particle data in the system's main memory and then use the CPU to update them. After the update, the vertices need to be transferred to the GPU's memory again so it can display the updated particles in the next frame. The most straight-forward way would be recreating the vertex buffer with the new data for each frame. This is obviously very costly. Depending on your implementation, there are other options like mapping GPU memory so it can be written by the CPU (called "resizable BAR" on desktop systems, or unified memory on integrated GPUs) or just using a host local buffer (which would be the slowest method due to PCI-E bandwidth). But no matter what buffer update method you choose, you always require a "round-trip" to the CPU to update the particles. + +With a GPU based particle system, this round-trip is no longer required. Vertices are only uploaded to the GPU at the start and all updates are done in the GPU's memory using compute shaders. One of the main reasons why this is faster is the much higher bandwidth between the GPU and it's local memory. In a CPU based scenario, you'd be limited by main memory and PCI-express bandwidth, which is often just a fraction of the GPU's memory bandwidth. + +When doing this on a GPU with a dedicated compute queue, you can update particles in parallel to the rendering part of the graphics pipeline. This is called "async compute", and is an advanced topic not covered in this tutorial. + +Here is a screenshot from this chapter's code. The particles shown here are updated by a compute shader directly on the GPU, without any CPU interaction: + +![](/images/compute_shader_particles.png) + +## Data manipulation + +In this tutorial we already learned about different buffer types like vertex and index buffers for passing primitives and uniform buffers for passing data to a shader. And we also used images to do texture mapping. But up until now, we always wrote data using the CPU and only did reads on the GPU. + +An important concept introduced with compute shaders is the ability to arbitrarily read from **and write to** buffers. For this, Vulkan offers two dedicated storage types. + +### Shader storage buffer objects (SSBO) + +A shader storage buffer (SSBO) allows shaders to read from and write to a buffer. Using these is similar to using uniform buffer objects. The biggest differences are that you can alias other buffer types to SSBOs and that they can be arbitrarily large. + +Going back to the GPU based particle system, you might now wonder how to deal with vertices being updated (written) by the compute shader and read (drawn) by the vertex shader, as both usages would seemingly require different buffer types. + +But that's not the case. In Vulkan you can specify multiple usages for buffers and images. So for the particle vertex buffer to be used as a vertex buffer (in the graphics pass) and as a storage buffer (in the compute pass), you simply create the buffer with those two usage flags: + +```c++ +VkBufferCreateInfo bufferInfo{}; +... +bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; +... + +if (vkCreateBuffer(device, &bufferInfo, nullptr, &shaderStorageBuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create vertex buffer!"); +} +``` +The two flags `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT` and `VK_BUFFER_USAGE_STORAGE_BUFFER_BIT` set with `bufferInfo.usage` tell the implementation that we want to use this buffer for two different scenarios: as a vertex buffer in the vertex shader and as a store buffer. Note that we also added the `VK_BUFFER_USAGE_TRANSFER_DST_BIT` flag in here so we can transfer data from the host to the GPU. This is crucial as we want the shader storage buffer to stay in GPU memory only (`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`) we need to to transfer data from the host to this buffer. + +Here is the same code using using the `createBuffer` helper function: + +```c++ +createBuffer(bufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, shaderStorageBuffers[i], shaderStorageBuffersMemory[i]); +``` + +The GLSL shader declaration for accessing such a buffer looks like this: + +```glsl +struct Particle { + vec2 position; + vec2 velocity; + vec4 color; +}; + +layout(std140, binding = 1) readonly buffer ParticleSSBOIn { + Particle particlesIn[ ]; +}; + +layout(std140, binding = 2) buffer ParticleSSBOOut { + Particle particlesOut[ ]; +}; +``` + +In this example we have a typed SSBO with each particle having a position and velocity value (see the `Particle` struct). The SSBO then contains an unbound number of particles as marked by the `[]`. Not having to specify the number of elements in an SSBO is one of the advantages over e.g. uniform buffers. `std140` is a memory layout qualifier that determines how the member elements of the shader storage buffer are aligned in memory. This gives us certain guarantees, required to map the buffers between the host and the GPU. + +Writing to such a storage buffer object in the compute shader is straight-forward and similar to how you'd write to the buffer on the C++ side: + +```glsl +particlesOut[index].position = particlesIn[index].position + particlesIn[index].velocity.xy * ubo.deltaTime; +``` + +### Storage images + +*Note that we won't be doing image manipulation in this chapter. This paragraph is here to make readers aware that compute shaders can also be used for image manipulation.* + +A storage image allows you read from and write to an image. Typical use cases are applying image effects to textures, doing post processing (which in turn is very similar) or generating mip-maps. + +This is similar for images: + +```c++ +VkImageCreateInfo imageInfo {}; +... +imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; +... + +if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); +} +``` + +The two flags `VK_IMAGE_USAGE_SAMPLED_BIT` and `VK_IMAGE_USAGE_STORAGE_BIT` set with `imageInfo.usage` tell the implementation that we want to use this image for two different scenarios: as an image sampled in the fragment shader and as a storage image in the computer shader; + +The GLSL shader declaration for storage image looks similar to sampled images used e.g. in the fragment shader: + +```glsl +layout (binding = 0, rgba8) uniform readonly image2D inputImage; +layout (binding = 1, rgba8) uniform writeonly image2D outputImage; +``` + +A few differences here are additional attributes like `rgba8` for the format of the image, the `readonly` and `writeonly` qualifiers, telling the implementation that we will only read from the input image and write to the output image. And last but not least we need to use the `image2D` type to declare a storage image. + +Reading from and writing to storage images in the compute shader is then done using `imageLoad` and `imageStore`: + +```glsl +vec3 pixel = imageLoad(inputImage, ivec2(gl_GlobalInvocationID.xy)).rgb; +imageStore(outputImage, ivec2(gl_GlobalInvocationID.xy), pixel); +``` + +## Compute queue families + +In the [physical device and queue families chapter](03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md#page_Queue-families) we already learned about queue families and how to select a graphics queue family. Compute uses the queue family properties flag bit `VK_QUEUE_COMPUTE_BIT`. So if we want to do compute work, we need to get a queue from a queue family that supports compute. + +Note that Vulkan requires an implementation which supports graphics operations to have at least one queue family that supports both graphics and compute operations, but it's also possible that implementations offer a dedicated compute queue. This dedicated compute queue (that does not have the graphics bit) hints at an asynchronous compute queue. To keep this tutorial beginner friendly though, we'll use a queue that can do both graphics and compute operations. This will also save us from dealing with several advanced synchronization mechanisms. + +For our compute sample we need to change the device creation code a bit: + +```c++ +uint32_t queueFamilyCount = 0; +vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + +std::vector queueFamilies(queueFamilyCount); +vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + +int i = 0; +for (const auto& queueFamily : queueFamilies) { + if ((queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) && (queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT)) { + indices.graphicsAndComputeFamily = i; + } + + i++; +} +``` + +The changed queue family index selection code will now try to find a queue family that supports both graphics and compute. + +We can then get a compute queue from this queue family in `createLogicalDevice`: + +```c++ +vkGetDeviceQueue(device, indices.graphicsAndComputeFamily.value(), 0, &computeQueue); +``` + +## The compute shader stage + +In the graphics samples we have used different pipeline stages to load shaders and access descriptors. Compute shaders are accessed in a similar way by using the `VK_SHADER_STAGE_COMPUTE_BIT` pipeline. So loading a compute shader is just the same as loading a vertex shader, but with a different shader stage. We'll talk about this in detail in the next paragraphs. Compute also introduces a new binding point type for descriptors and pipelines named `VK_PIPELINE_BIND_POINT_COMPUTE` that we'll have to use later on. + +## Loading compute shaders + +Loading compute shaders in our application is the same as loading any other other shader. The only real difference is that we'll need to use the `VK_SHADER_STAGE_COMPUTE_BIT` mentioned above. + +```c++ +auto computeShaderCode = readFile("shaders/compute.spv"); + +VkShaderModule computeShaderModule = createShaderModule(computeShaderCode); + +VkPipelineShaderStageCreateInfo computeShaderStageInfo{}; +computeShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; +computeShaderStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; +computeShaderStageInfo.module = computeShaderModule; +computeShaderStageInfo.pName = "main"; +... +``` + +## Preparing the shader storage buffers + +Earlier on we learned that we can use shader storage buffers to pass arbitrary data to compute shaders. For this example we will upload an array of particles to the GPU, so we can manipulate it directly in the GPU's memory. + +In the [frames in flight](03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md) chapter we talked about duplicating resources per frame in flight, so we can keep the CPU and the GPU busy. First we declare a vector for the buffer object and the device memory backing it up: + +```c++ +std::vector shaderStorageBuffers; +std::vector shaderStorageBuffersMemory; +``` + +In the `createShaderStorageBuffers` we then resize those vectors to match the max. number of frames in flight: + +```c++ +shaderStorageBuffers.resize(MAX_FRAMES_IN_FLIGHT); +shaderStorageBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); +``` + +With this setup in place we can start to move the initial particle information to the GPU. We first initialize a vector of particles on the host side: + +```c++ + // Initialize particles + std::default_random_engine rndEngine((unsigned)time(nullptr)); + std::uniform_real_distribution rndDist(0.0f, 1.0f); + + // Initial particle positions on a circle + std::vector particles(PARTICLE_COUNT); + for (auto& particle : particles) { + float r = 0.25f * sqrt(rndDist(rndEngine)); + float theta = rndDist(rndEngine) * 2 * 3.14159265358979323846; + float x = r * cos(theta) * HEIGHT / WIDTH; + float y = r * sin(theta); + particle.position = glm::vec2(x, y); + particle.velocity = glm::normalize(glm::vec2(x,y)) * 0.00025f; + particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); + } + +``` + +We then create a [staging buffer](04_Vertex_buffers/02_Staging_buffer.md) in the host's memory to hold the initial particle properties: + +```c++ + VkDeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, particles.data(), (size_t)bufferSize); + vkUnmapMemory(device, stagingBufferMemory); +``` + +Using this staging buffer as a source we then create the per-frame shader storage buffers and copy the particle properties from the staging buffer to each of these: + +```c++ + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, shaderStorageBuffers[i], shaderStorageBuffersMemory[i]); + // Copy data from the staging buffer (host) to the shader storage buffer (GPU) + copyBuffer(stagingBuffer, shaderStorageBuffers[i], bufferSize); + } +} +``` + +## Descriptors + +Setting up descriptors for compute is almost identical to graphics. The only difference is that descriptors need to have the `VK_SHADER_STAGE_COMPUTE_BIT` set to make them accessible by the compute stage: + +```c++ +std::array layoutBindings{}; +layoutBindings[0].binding = 0; +layoutBindings[0].descriptorCount = 1; +layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +layoutBindings[0].pImmutableSamplers = nullptr; +layoutBindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; +... +``` + +Note that you can combine shader stages here, so if you want the descriptor to be accessible from the vertex and compute stage, e.g. for a uniform buffer with parameters shared across them, you simply set the bits for both stages: + +```c++ +layoutBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT; +``` + +Here is the descriptor setup for our sample. The layout looks like this: + +```c++ +std::array layoutBindings{}; +layoutBindings[0].binding = 0; +layoutBindings[0].descriptorCount = 1; +layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +layoutBindings[0].pImmutableSamplers = nullptr; +layoutBindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + +layoutBindings[1].binding = 1; +layoutBindings[1].descriptorCount = 1; +layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; +layoutBindings[1].pImmutableSamplers = nullptr; +layoutBindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + +layoutBindings[2].binding = 2; +layoutBindings[2].descriptorCount = 1; +layoutBindings[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; +layoutBindings[2].pImmutableSamplers = nullptr; +layoutBindings[2].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + +VkDescriptorSetLayoutCreateInfo layoutInfo{}; +layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; +layoutInfo.bindingCount = 3; +layoutInfo.pBindings = layoutBindings.data(); + +if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create compute descriptor set layout!"); +} +``` + +Looking at this setup, you might wonder why we have two layout bindings for shader storage buffer objects, even though we'll only render a single particle system. This is because the particle positions are updated frame by frame based on a delta time. This means that each frame needs to know about the last frames' particle positions, so it can update them with a new delta time and write them to it's own SSBO: + +![](/images/compute_ssbo_read_write.svg) + +For that, the compute shader needs to have access to the last and current frame's SSBOs. This is done by passing both to the compute shader in our descriptor setup. See the `storageBufferInfoLastFrame` and `storageBufferInfoCurrentFrame`: + +```c++ +for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo uniformBufferInfo{}; + uniformBufferInfo.buffer = uniformBuffers[i]; + uniformBufferInfo.offset = 0; + uniformBufferInfo.range = sizeof(UniformBufferObject); + + std::array descriptorWrites{}; + ... + + VkDescriptorBufferInfo storageBufferInfoLastFrame{}; + storageBufferInfoLastFrame.buffer = shaderStorageBuffers[(i - 1) % MAX_FRAMES_IN_FLIGHT]; + storageBufferInfoLastFrame.offset = 0; + storageBufferInfoLastFrame.range = sizeof(Particle) * PARTICLE_COUNT; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = computeDescriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pBufferInfo = &storageBufferInfoLastFrame; + + VkDescriptorBufferInfo storageBufferInfoCurrentFrame{}; + storageBufferInfoCurrentFrame.buffer = shaderStorageBuffers[i]; + storageBufferInfoCurrentFrame.offset = 0; + storageBufferInfoCurrentFrame.range = sizeof(Particle) * PARTICLE_COUNT; + + descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[2].dstSet = computeDescriptorSets[i]; + descriptorWrites[2].dstBinding = 2; + descriptorWrites[2].dstArrayElement = 0; + descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[2].descriptorCount = 1; + descriptorWrites[2].pBufferInfo = &storageBufferInfoCurrentFrame; + + vkUpdateDescriptorSets(device, 3, descriptorWrites.data(), 0, nullptr); +} +``` + +Remember that we also have to request the descriptor types for the SSBOs from our descriptor pool: + +```c++ +std::array poolSizes{}; +... + +poolSizes[1].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; +poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT) * 2; +``` + +We need to double the number of `VK_DESCRIPTOR_TYPE_STORAGE_BUFFER` types requested from the pool by two because our sets reference the SSBOs of the last and current frame. + +## Compute pipelines + +As compute is not a part of the graphics pipeline, we can't use `vkCreateGraphicsPipelines`. Instead we need to create a dedicated compute pipeline with `vkCreateComputePipelines` for running our compute commands. Since a compute pipeline does not touch any of the rasterization state, it has a lot less state than a graphics pipeline: + +```c++ +VkComputePipelineCreateInfo pipelineInfo{}; +pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; +pipelineInfo.layout = computePipelineLayout; +pipelineInfo.stage = computeShaderStageInfo; + +if (vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &computePipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create compute pipeline!"); +} +``` + +The setup is a lot simpler, as we only require one shader stage and a pipeline layout. The pipeline layout works the same as with the graphics pipeline: + +```c++ +VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; +pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +pipelineLayoutInfo.setLayoutCount = 1; +pipelineLayoutInfo.pSetLayouts = &computeDescriptorSetLayout; + +if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &computePipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create compute pipeline layout!"); +} +``` + +## Compute space + +Before we get into how a compute shader works and how we submit compute workloads to the GPU, we need to talk about two important compute concepts: **work groups** and **invocations**. They define an abstract execution model for how compute workloads are processed by the compute hardware of the GPU in three dimensions (x, y, and z). + +**Work groups** define how the compute workloads are formed and processed by the the compute hardware of the GPU. You can think of them as work items the GPU has to work through. Work group dimensions are set by the application at command buffer time using a dispatch command. + +And each work group then is a collection of **invocations** that execute the same compute shader. Invocations can potentially run in parallel and their dimensions are set in the compute shader. Invocations within a single workgroup have access to shared memory. + +This image shows the relation between these two in three dimensions: + +![](/images/compute_space.svg) + +The number of dimensions for work groups (defined by `vkCmdDispatch`) and invocations depends (defined by the local sizes in the compute shader) on how input data is structured. If you e.g. work on a one-dimensional array, like we do in this chapter, you only have to specify the x dimension for both. + +As an example: If we dispatch a work group count of [64, 1, 1] with a compute shader local size of [32, 32, ,1], our compute shader will be invoked 64 x 32 x 32 = 65,536 times. + +Note that the maximum count for work groups and local sizes differs from implementation to implementation, so you should always check the compute related `maxComputeWorkGroupCount`, `maxComputeWorkGroupInvocations` and `maxComputeWorkGroupSize` limits in `VkPhysicalDeviceLimits`. + +## Compute shaders + +Now that we have learned about all the parts required to setup a compute shader pipeline, it's time to take a look at compute shaders. All of the things we learned about using GLSL shaders e.g. for vertex and fragment shaders also applies to compute shaders. The syntax is the same, and many concepts like passing data between the application and the shader are the same. But there are some important differences. + +A very basic compute shader for updating a linear array of particles may look like this: + +```glsl +#version 450 + +layout (binding = 0) uniform ParameterUBO { + float deltaTime; +} ubo; + +struct Particle { + vec2 position; + vec2 velocity; + vec4 color; +}; + +layout(std140, binding = 1) readonly buffer ParticleSSBOIn { + Particle particlesIn[ ]; +}; + +layout(std140, binding = 2) buffer ParticleSSBOOut { + Particle particlesOut[ ]; +}; + +layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in; + +void main() +{ + uint index = gl_GlobalInvocationID.x; + + Particle particleIn = particlesIn[index]; + + particlesOut[index].position = particleIn.position + particleIn.velocity.xy * ubo.deltaTime; + particlesOut[index].velocity = particleIn.velocity; + ... +} +``` + +The top part of the shader contains the declarations for the shader's input. First is a uniform buffer object at binding 0, something we already learned about in this tutorial. Below we declare our Particle structure that matches the declaration in the C++ code. Binding 1 then refers to the shader storage buffer object with the particle data from the last frame (see the descriptor setup), and binding 2 points to the SSBO for the current frame, which is the one we'll be updating with this shader. + +An interesting thing is this compute-only declaration related to the compute space: + +```glsl +layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in; +``` +This defines the number invocations of this compute shader in the current work group. As noted earlier, this is the local part of the compute space. Hence the `local_` prefix. As we work on a linear 1D array of particles we only need to specify a number for x dimension in `local_size_x`. + +The `main` function then reads from the last frame's SSBO and writes the updated particle position to the SSBO for the current frame. Similar to other shader types, compute shaders have their own set of builtin input variables. Built-ins are always prefixed with `gl_`. One such built-in is `gl_GlobalInvocationID`, a variable that uniquely identifies the current compute shader invocation across the current dispatch. We use this to index into our particle array. + +## Running compute commands + +### Dispatch + +Now it's time to actually tell the GPU to do some compute. This is done by calling `vkCmdDispatch` inside a command buffer. While not perfectly true, a dispatch is for compute as a draw call like `vkCmdDraw` is for graphics. This dispatches a given number of compute work items in at max. three dimensions. + +```c++ +VkCommandBufferBeginInfo beginInfo{}; +beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + +if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); +} + +... + +vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline); +vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &computeDescriptorSets[i], 0, 0); + +vkCmdDispatch(computeCommandBuffer, PARTICLE_COUNT / 256, 1, 1); + +... + +if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); +} +``` + +The `vkCmdDispatch` will dispatch `PARTICLE_COUNT / 256` local work groups in the x dimension. As our particles array is linear, we leave the other two dimensions at one, resulting in a one-dimensional dispatch. But why do we divide the number of particles (in our array) by 256? That's because in the previous paragraph we defined that every compute shader in a work group will do 256 invocations. So if we were to have 4096 particles, we would dispatch 16 work groups, with each work group running 256 compute shader invocations. Getting the two numbers right usually takes some tinkering and profiling, depending on your workload and the hardware you're running on. If your particle size would be dynamic and can't always be divided by e.g. 256, you can always use `gl_GlobalInvocationID` at the start of your compute shader and return from it if the global invocation index is greater than the number of your particles. + +And just as was the case for the compute pipeline, a compute command buffer contains a lot less state than a graphics command buffer. There's no need to start a render pass or set a viewport. + +### Submitting work + +As our sample does both compute and graphics operations, we'll be doing two submits to both the graphics and compute queue per frame (see the `drawFrame` function): + +```c++ +... +if (vkQueueSubmit(computeQueue, 1, &submitInfo, nullptr) != VK_SUCCESS) { + throw std::runtime_error("failed to submit compute command buffer!"); +}; +... +if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); +} +``` + +The first submit to the compute queue updates the particle positions using the compute shader, and the second submit will then use that updated data to draw the particle system. + +### Synchronizing graphics and compute + +Synchronization is an important part of Vulkan, even more so when doing compute in conjunction with graphics. Wrong or lacking synchronization may result in the vertex stage starting to draw (=read) particles while the compute shader hasn't finished updating (=write) them (read-after-write hazard), or the compute shader could start updating particles that are still in use by the vertex part of the pipeline (write-after-read hazard). + +So we must make sure that those cases don't happen by properly synchronizing the graphics and the compute load. There are different ways of doing so, depending on how you submit your compute workload but in our case with two separate submits, we'll be using [semaphores](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Semaphores) and [fences](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Fences) to ensure that the vertex shader won't start fetching vertices until the compute shader has finished updating them. + +This is necessary as even though the two submits are ordered one-after-another, there is no guarantee that they execute on the GPU in this order. Adding in wait and signal semaphores ensures this execution order. + +So we first add a new set of synchronization primitives for the compute work in `createSyncObjects`. The compute fences, just like the graphics fences, are created in the signaled state because otherwise, the first draw would time out while waiting for the fences to be signaled as detailed [here](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Waiting-for-the-previous-frame): + +```c++ +std::vector computeInFlightFences; +std::vector computeFinishedSemaphores; +... +computeInFlightFences.resize(MAX_FRAMES_IN_FLIGHT); +computeFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + +VkSemaphoreCreateInfo semaphoreInfo{}; +semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + +VkFenceCreateInfo fenceInfo{}; +fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; +fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + +for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + ... + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &computeFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &computeInFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create compute synchronization objects for a frame!"); + } +} +``` +We then use these to synchronize the compute buffer submission with the graphics submission: + +```c++ +// Compute submission +vkWaitForFences(device, 1, &computeInFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + +updateUniformBuffer(currentFrame); + +vkResetFences(device, 1, &computeInFlightFences[currentFrame]); + +vkResetCommandBuffer(computeCommandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); +recordComputeCommandBuffer(computeCommandBuffers[currentFrame]); + +submitInfo.commandBufferCount = 1; +submitInfo.pCommandBuffers = &computeCommandBuffers[currentFrame]; +submitInfo.signalSemaphoreCount = 1; +submitInfo.pSignalSemaphores = &computeFinishedSemaphores[currentFrame]; + +if (vkQueueSubmit(computeQueue, 1, &submitInfo, computeInFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit compute command buffer!"); +}; + +// Graphics submission +vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + +... + +vkResetFences(device, 1, &inFlightFences[currentFrame]); + +vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); +recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + +VkSemaphore waitSemaphores[] = { computeFinishedSemaphores[currentFrame], imageAvailableSemaphores[currentFrame] }; +VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; +submitInfo = {}; +submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + +submitInfo.waitSemaphoreCount = 2; +submitInfo.pWaitSemaphores = waitSemaphores; +submitInfo.pWaitDstStageMask = waitStages; +submitInfo.commandBufferCount = 1; +submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; +submitInfo.signalSemaphoreCount = 1; +submitInfo.pSignalSemaphores = &renderFinishedSemaphores[currentFrame]; + +if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); +} +``` + +Similar to the sample in the [semaphores chapter](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Semaphores), this setup will immediately run the compute shader as we haven't specified any wait semaphores. This is fine, as we are waiting for the compute command buffer of the current frame to finish execution before the compute submission with the `vkWaitForFences` command. + +The graphics submission on the other hand needs to wait for the compute work to finish so it doesn't start fetching vertices while the compute buffer is still updating them. So we wait on the `computeFinishedSemaphores` for the current frame and have the graphics submission wait on the `VK_PIPELINE_STAGE_VERTEX_INPUT_BIT` stage, where vertices are consumed. + +But it also needs to wait for presentation so the fragment shader won't output to the color attachments until the image has been presented. So we also wait on the `imageAvailableSemaphores` on the current frame at the `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage. + +## Drawing the particle system + +Earlier on, we learned that buffers in Vulkan can have multiple use-cases and so we created the shader storage buffer that contains our particles with both the shader storage buffer bit and the vertex buffer bit. This means that we can use the shader storage buffer for drawing just as we used "pure" vertex buffers in the previous chapters. + +We first setup the vertex input state to match our particle structure: + +```c++ +struct Particle { + ... + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Particle, position); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Particle, color); + + return attributeDescriptions; + } +}; +``` + +Note that we don't add `velocity` to the vertex input attributes, as this is only used by the compute shader. + +We then bind and draw it like we would with any vertex buffer: + +```c++ +vkCmdBindVertexBuffers(commandBuffer, 0, 1, &shaderStorageBuffer[currentFrame], offsets); + +vkCmdDraw(commandBuffer, PARTICLE_COUNT, 1, 0, 0); +``` + +## Conclusion + +In this chapter, we learned how to use compute shaders to offload work from the CPU to the GPU. Without compute shaders, many effects in modern games and applications would either not be possible or would run a lot slower. But even more than graphics, compute has a lot of use-cases, and this chapter only gives you a glimpse of what's possible. So now that you know how to use compute shaders, you may want to take look at some advanced compute topics like: + +- Shared memory +- [Asynchronous compute](https://github.com/KhronosGroup/Vulkan-Samples/tree/master/samples/performance/async_compute) +- Atomic operations +- [Subgroups](https://www.khronos.org/blog/vulkan-subgroup-tutorial) + +You can find some advanced compute samples in the [official Khronos Vulkan Samples repository](https://github.com/KhronosGroup/Vulkan-Samples/tree/master/samples/api). + +[C++ code](/code/31_compute_shader.cpp) / +[Vertex shader](/code/31_shader_compute.vert) / +[Fragment shader](/code/31_shader_compute.frag) / +[Compute shader](/code/31_shader_compute.comp) diff --git a/en/90_FAQ.md b/en/90_FAQ.md new file mode 100644 index 00000000..3378362a --- /dev/null +++ b/en/90_FAQ.md @@ -0,0 +1,58 @@ +This page lists solutions to common problems that you may encounter while +developing Vulkan applications. + +## I get an access violation error in the core validation layer + +Make sure +that MSI Afterburner / RivaTuner Statistics Server is not running, because it +has some compatibility problems with Vulkan. + +## I don't see any messages from the validation layers / Validation layers are not available + +First make sure that the validation layers get a chance to print errors by keeping the +terminal open after your program exits. You can do this from Visual Studio by running +your program with Ctrl-F5 instead of F5, and on Linux by executing your program from +a terminal window. If there are still no messages and you are sure that validation +layers are turned on, then you should ensure that your Vulkan SDK is correctly +installed by following the "Verify the Installation" instructions [on this page](https://vulkan.lunarg.com/doc/view/1.2.135.0/windows/getting_started.html). Also ensure that your SDK version is at least 1.1.106.0 to support the `VK_LAYER_KHRONOS_validation` layer. + +## vkCreateSwapchainKHR triggers an error in SteamOverlayVulkanLayer64.dll + +This appears to be a compatibility problem in the Steam client beta. There are a +few possible workarounds: + * Opt out of the Steam beta program. + * Set the `DISABLE_VK_LAYER_VALVE_steam_overlay_1` environment variable to `1` + * Delete the Steam overlay Vulkan layer entry in the registry under `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers` + +Example: + +![](/images/steam_layers_env.png) + +## vkCreateInstance fails with VK_ERROR_INCOMPATIBLE_DRIVER + +If you are using MacOS with the latest MoltenVK SDK then `vkCreateInstance` may return the `VK_ERROR_INCOMPATIBLE_DRIVER` error. This is because [Vulkan SDK version 1.3.216 or newer](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html) requires you to enable the `VK_KHR_PORTABILITY_subset` extension to use MoltenVK, because it is currently not fully conformant. + +You have to add the `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` flag to your `VkInstanceCreateInfo` and add `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME` to your instance extension list. + +Code example: + +```c++ +... + +std::vector requiredExtensions; + +for(uint32_t i = 0; i < glfwExtensionCount; i++) { + requiredExtensions.emplace_back(glfwExtensions[i]); +} + +requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + +createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + +createInfo.enabledExtensionCount = (uint32_t) requiredExtensions.size(); +createInfo.ppEnabledExtensionNames = requiredExtensions.data(); + +if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); +} +``` diff --git a/en/95_Privacy_policy.md b/en/95_Privacy_policy.md new file mode 100644 index 00000000..fc5cad78 --- /dev/null +++ b/en/95_Privacy_policy.md @@ -0,0 +1,21 @@ +## General + +This privacy policy applies to the information that is collected when you use vulkan-tutorial.com or any of its subdomains. It describes how the owner of this website, Alexander Overvoorde, collects, uses and shares information about you. + +## Analytics + +This website collects analytics about visitors using a self-hosted instance of Matomo ([https://matomo.org/](https://matomo.org/)), formerly known as Piwik. It records which pages you visit, what type of device and browser you use, how long you view a given page and where you came from. This information is anonymized by only recording the first two bytes of your IP address (e.g. `123.123.xxx.xxx`). These anonymized logs are stored for an indefinite amount of time. + +These analytics are used for the purpose of tracking how content on the website is consumed, how many people visit the website in general, and which other websites link here. This makes it easier to engage with the community and determine which areas of the website should be improved, for example if extra time should be spent on facilitating mobile reading. + +This data is not shared with third parties. + +## Advertisement + +This website uses a third-party advertisement server that may use cookies to track activities on the website to measure engagement with advertisements. + +## Comments + +Each chapter includes a comment section at the end that is provided by the third-party Disqus service. This service collects identity data to facilitate the reading and submission of comments, and aggregate usage information to improve their service. + +The full privacy policy of this third-party service can be found at [https://help.disqus.com/terms-and-policies/disqus-privacy-policy](https://help.disqus.com/terms-and-policies/disqus-privacy-policy). \ No newline at end of file diff --git a/fr/00_Introduction.md b/fr/00_Introduction.md new file mode 100644 index 00000000..f1d28102 --- /dev/null +++ b/fr/00_Introduction.md @@ -0,0 +1,119 @@ +>NOTICE: The English version of the tutorial has recently changed significantly (for the better) and these changes have not yet been applied to the French translation. + +## À propos + +Ce tutoriel vous enseignera les bases de l'utilisation de l'API [Vulkan](https://www.khronos.org/vulkan/) qui expose +les graphismes et le calcul sur cartes graphiques. Vulkan est une nouvelle API créée par le +[groupe Khronos](https://www.khronos.org/) (connu pour OpenGL). Elle fournit une bien meilleure abstraction des cartes +graphiques modernes. Cette nouvelle interface vous permet de mieux décrire ce que votre application souhaite faire, +ce qui peut mener à de meilleures performances et à des comportements moins variables comparés à des APIs +existantes comme [OpenGL](https://en.wikipedia.org/wiki/OpenGL) et +[Direct3D](https://en.wikipedia.org/wiki/Direct3D). Les concepts introduits par Vulkan sont similaires à ceux de +[Direct3D 12](https://en.wikipedia.org/wiki/Direct3D#Direct3D_12) et [Metal](https://en.wikipedia.org/wiki/Metal_(API)). +Cependant Vulkan a l'avantage d'être complètement cross-platform, et vous permet ainsi de développer pour Windows, +Linux, Mac et Android en même temps. + +Il y a cependant un contre-coup à ces avantages. L'API vous impose d'être explicite sur chaque détail. Vous ne pourrez +rien laisser au hasard, et il n'y a aucune structure, aucun environnement créé pour vous par défaut. Il faudra le +recréer à partir de rien. Le travail du driver graphique sera ainsi considérablement réduit, ce qui implique un plus +grand travail de votre part pour assurer un comportement correct. + +Le message véhiculé ici est que Vulkan n'est pas fait pour tout le monde. Cette API est conçue pour les programmeurs +concernés par la programmation avec GPU de haute performance, et qui sont prêts à y travailler sérieusement. Si vous +êtes intéressées dans le développement de jeux vidéo, et moins dans les graphismes eux-mêmes, vous devriez plutôt +continuer d'utiliser OpenGL et DirectX, qui ne seront pas dépréciés en faveur de Vulkan avant un certain temps. Une +autre alternative serait d'utiliser un moteur de jeu comme +[Unreal Engine](https://en.wikipedia.org/wiki/Unreal_Engine#Unreal_Engine_4) ou +[Unity](https://en.wikipedia.org/wiki/Unity_(game_engine)), qui pourront être capables d'utiliser Vulkan tout en +exposant une API de bien plus haut niveau. + +Cela étant dit, présentons quelques prérequis pour ce tutoriel: + +* Une carte graphique et un driver compatibles avec Vulkan ([NVIDIA](https://developer.nvidia.com/vulkan-driver), +[AMD](https://www.amd.com/en/technologies/vulkan), +[Intel](https://software.intel.com/en-us/blogs/2017/02/10/intel-announces-that-we-are-moving-from-beta-support-to-full-official-support-for)) +* De l'expérience avec le C++ (familiarité avec RAII, listes d'initialisation, et autres fonctionnalités modernes) +* Un compilateur avec un support décent des fonctionnalités du C++17 (Visual Studio 2017+, GCC 7+ ou Clang 5+) +* Un minimum d'expérience dans le domaine de la programmation graphique + +Ce tutoriel ne considérera pas comme acquis les concepts d'OpenGL et de Direct3D, mais il requiert que vous connaissiez +les bases du rendu 3D. Il n'expliquera pas non plus les mathématiques derrière la projection de perspective, par +exemple. Lisez [ce livre](http://opengl.datenwolf.net/gltut/html/index.html) pour une bonne introduction des concepts +de rendu 3D. D'autres ressources pour le développement d'application graphiques sont : +* [Ray tracing en un week-end](https://github.com/petershirley/raytracinginoneweekend) +* [Livre sur le Physical Based Rendering](http://www.pbr-book.org/) +* Une application de Vulkan dans les moteurs graphiques open source [Quake](https://github.com/Novum/vkQuake) et de +[DOOM 3](https://github.com/DustinHLand/vkDOOM3) + +Vous pouvez utiliser le C plutôt que le C++ si vous le souhaitez, mais vous devrez utiliser une autre bibliothèque +d'algèbre linéaire et vous structurerez vous-même votre code. Nous utiliserons des possibilités du C++ (RAII, +classes) pour organiser la logique et la durée de vie des ressources. Il existe aussi une +[version alternative](https://github.com/bwasty/vulkan-tutorial-rs) de ce tutoriel pour les développeurs rust. + +Pour faciliter la tâche des développeurs utilisant d'autres langages de programmation, et pour acquérir de l'expérience +avec l'API de base, nous allons utiliser l'API C originelle pour travailler avec Vulkan. Cependant, si vous utilisez le +C++, vous pourrez préférer utiliser le binding [Vulkan-Hpp](https://github.com/KhronosGroup/Vulkan-Hpp) plus récent, +qui permet de s'éloigner de certains détails ennuyeux et d'éviter certains types d'erreurs. + +## E-book + +Si vous préférez lire ce tutoriel en E-book, vous pouvez en télécharger une version EPUB ou PDF ici: + +* [EPUB](https://vulkan-tutorial.com/resources/vulkan_tutorial_fr.epub) +* [PDF](https://vulkan-tutorial.com/resources/vulkan_tutorial_fr.pdf) + +## Structure du tutoriel + +Nous allons commencer par une approche générale du fonctionnement de Vulkan, et verrons d'abord rapidement le travail à +effectuer pour afficher un premier triangle à l'écran. Le but de chaque petite étape aura ainsi plus de sens quand +vous aurez compris leur rôle dans le fonctionnement global. Ensuite, nous préparerons l'environnement de développement, +avec le [SDK Vulkan](https://lunarg.com/vulkan-sdk/), la [bibliothèque GLM](http://glm.g-truc.net/) pour les opérations +d'algèbre linéaire, et [GLFW](http://www.glfw.org/) pour la création d'une fenêtre. Ce tutoriel couvrira leur mise en +place sur Windows avec Visual Studio, sur Linux Ubuntu avec GCC et sur MacOS. + +Après cela, nous implémenterons tous les éléments nécessaires à un programme Vulkan pour afficher votre premier +triangle. Chaque chapitre suivra approximativement la structure suivante : + +* Introduction d'un nouveau concept et de son utilité +* Utilisation de tous les appels correspondants à l'API pour leur mise en place dans votre programme +* Placement d'une partie de ces appels dans des fonctions pour une réutilisation future + +Bien que chaque chapitre soit écrit comme suite du précédent, il est également possible de lire chacun d'entre eux +comme un article introduisant une certaine fonctionnalité de Vulkan. Ainsi le site peut vous être utile comme référence. +Toutes les fonctions et les types Vulkan sont liés à leur spécification, vous pouvez donc cliquer dessus pour en +apprendre plus. La spécification est par contre en Anglais. Vulkan est une API récente, il peut donc y avoir des +lacunes dans la spécification elle-même. Vous êtes encouragés à transmettre vos retours dans +[ce repo Khronos](https://github.com/KhronosGroup/Vulkan-Docs). + +Comme indiqué plus haut, Vulkan est une API assez prolixe, avec de nombreux paramètres, pensés pour vous fournir un +maximum de contrôle sur le hardware graphique. Ainsi des opérations comme créer une texture prennent de nombreuses +étapes qui doivent être répétées chaque fois. Nous créerons notre propre collection de fonctions d'aide tout le long +du tutoriel. + +Chaque chapitre se conclura avec un lien menant à la totalité du code écrit jusqu'à ce point. Vous pourrez vous y +référer si vous avez un quelconque doute quant à la structure du code, ou si vous rencontrez un bug et que voulez +comparer. Tous les fichiers de code ont été testés sur des cartes graphiques de différents vendeurs pour pouvoir +affirmer qu'ils fonctionnent. Chaque chapitre possède également une section pour écrire vos commentaires en relation +avec le sujet discuté. Veuillez y indiquer votre plateforme, la version de votre driver, votre code source, le +comportement attendu et celui obtenu pour nous simplifier la tâche de vous aider. + +Ce tutoriel est destiné à être un effort de communauté. Vulkan est encore une API très récente et les meilleures +manières d'arriver à un résultat n'ont pas encore été déterminées. Si vous avez un quelconque retour sur le tutoriel +et le site lui-même, n'hésitez alors pas à créer une issue ou une pull request sur le +[repo GitHub](https://github.com/Overv/VulkanTutorial). Vous pouvez *watch* le dépôt afin d'être notifié des +dernières mises à jour du site. + +Après avoir accompli le rituel de l'affichage de votre premier triangle avec Vulkan, nous étendrons le programme pour y +inclure les transformations linéaires, les textures et les modèles 3D. + +Si vous avez déjà utilisé une API graphique auparavant, vous devez savoir qu'il y a nombre d'étapes avant d'afficher la +première géométrie sur l'écran. Il y aura beaucoup plus de ces étapes préliminaires avec Vulkan, mais vous verrez que +chacune d'entre elle est simple à comprendre et n'est pas redondante. Gardez aussi à l'esprit qu'une fois que vous savez +afficher un triangle - certes peu intéressant -, afficher un modèle 3D parfaitement texturé ne nécessite pas tant de +travail supplémentaire, et que chaque étape à partir de ce point est bien mieux récompensée visuellement. + +Si vous rencontrez un problème en suivant ce tutoriel, vérifiez d'abord dans la FAQ que votre problème et sa solution +n'y sont pas déjà listés. Si vous êtes toujours coincé après cela, demandez de l'aide dans la section des commentaires +du chapitre le plus en lien avec votre problème. + +Prêt à vous lancer dans le futur des API graphiques de haute performance? [Allons-y!](!fr/Introduction) diff --git a/fr/01_Vue_d'ensemble.md b/fr/01_Vue_d'ensemble.md new file mode 100644 index 00000000..b5a9ea26 --- /dev/null +++ b/fr/01_Vue_d'ensemble.md @@ -0,0 +1,231 @@ +Ce chapitre commencera par introduire Vulkan et les problèmes auxquels l'API s’adresse. Nous nous intéresserons ensuite aux +éléments requis pour l'affichage d'un premier triangle. Cela vous donnera une vue d'ensemble pour mieux replacer les +futurs chapitres dans leur contexte. Nous conclurons sur la structure de Vulkan et la manière dont l'API est communément +utilisée. + +## Origine de Vulkan + +Comme les APIs précédentes, Vulkan est conçue comme une abstraction des +[GPUs](https://en.wikipedia.org/wiki/Graphics_processing_unit). Le problème avec la plupart de ces APIs est qu'elles +furent créées à une époque où le hardware graphique était limité à des fonctionnalités prédéfinies tout juste +configurables. Les développeurs devaient fournir les sommets dans un format standardisé, et étaient ainsi à la merci +des constructeurs pour les options d'éclairage et les jeux d'ombre. + +Au fur et à mesure que les cartes graphiques progressèrent, elles offrirent de plus en plus de fonctionnalités +programmables. Il fallait alors intégrer toutes ces nouvelles fonctionnalités aux APIs existantes. Ceci résulta +en une abstraction peu pratique et le driver devait deviner l'intention du développeur pour relier le programme aux +architectures modernes. C'est pour cela que les drivers étaient mis à jour si souvent, et que certaines augmentaient +soudainement les performances. À cause de la complexité de ces drivers, les développeurs devaient gérer les +différences de comportement entre les fabricants, dont par exemple des tolérances plus ou moins importantes pour les +[shaders](https://en.wikipedia.org/wiki/Shader). Un exemple de fonctionnalité est le +[tiled rendering](https://en.wikipedia.org/wiki/Tiled_rendering), pour laquelle une plus grande flexibilité mènerait à +de meilleures performance. Ces APIs anciennes souffrent également d’une autre limitation : le support limité du +multithreading, menant à des goulot d'étranglement du coté du CPU. Au-delà des nouveautés techniques, la dernière +décennie a aussi été témoin de l’arrivée de matériel mobile. Ces GPUs portables ont des architectures différentes qui +prennent en compte des contraintes spatiales ou énergétiques. + +Vulkan résout ces problèmes en ayant été repensée à partir de rien pour des architectures modernes. Elle réduit le +travail du driver en permettant (en fait en demandant) au développeur d’expliciter ses objectifs en passant par une +API plus prolixe. Elle permet à plusieurs threads d’invoquer des commandes de manière asynchrone. Elle supprime les +différences lors de la compilation des shaders en imposant un format en bytecode compilé par un compilateur officiel. +Enfin, elle reconnaît les capacités des cartes graphiques modernes en unifiant le computing et les graphismes dans +une seule et unique API. + +## Le nécessaire pour afficher un triangle + +Nous allons maintenant nous intéresser aux étapes nécessaires à l’affichage d’un triangle dans un programme Vulkan +correctement conçu. Tous les concepts ici évoqués seront développés dans les prochains chapitres. Le but ici est +simplement de vous donner une vue d’ensemble afin d’y replacer tous les éléments. + +### Étape 1 - Instance et sélection d’un physical device + +Une application commence par paramétrer l’API à l’aide d’une «`VkInstance`». Une instance est créée en décrivant votre +application et les extensions que vous comptez utiliser. Après avoir créé votre `VkInstance`, vous pouvez demander l’accès +à du hardware compatible avec Vulkan, et ainsi sélectionner un ou plusieurs «`VkPhysicalDevice`» pour y réaliser vos +opérations. Vous pouvez traiter des informations telles que la taille de la VRAM ou des capacités de la carte graphique, +et ainsi préférer par exemple du matériel dédié. + +### Étape 2 – Logical device et familles de queues (queue families) + +Après avoir sélectionné le hardware qui vous convient, vous devez créer un `VkDevice` (logical device). Vous décrivez +pour cela quelles `VkPhysicalDeviceFeatures` vous utiliserez, comme l’affichage multi-fenêtre ou des floats de 64 bits. +Vous devrez également spécifier quelles `vkQueueFamilies` vous utiliserez. La plupart des opérations, comme les +commandes d’affichage et les allocations de mémoire, sont exécutés de manière asynchrone en les envoyant à une +`VkQueue`. Ces queues sont crées à partir d’une famille de queues, chacune de ces dernières supportant uniquement une +certaine collection d’opérations. Il pourrait par exemple y avoir des familles différentes pour les graphismes, le +calcul et les opérations mémoire. L’existence d’une famille peut aussi être un critère pour la sélection d’un physical +device. En effet une queue capable de traiter les commandes graphiques et opérations mémoire permet d'augmenter +encore un peu les performances. Il sera possible qu’un périphérique supportant Vulkan ne fournisse aucun graphisme, +mais à ce jour toutes les opérations que nous allons utiliser devraient être disponibles. + +### Étape 3 – Surface d’affichage (window surface) et swap chain + +À moins que vous ne soyez intéressé que par le rendu off-screen, vous devrez créer une fenêtre dans laquelle afficher +les éléments. Les fenêtres peuvent être crées avec les APIs spécifiques aux différentes plateformes ou avec des +librairies telles que [GLFW](http://www.glfw.org/) et [SDL](https://www.libsdl.org/). Nous utiliserons GLFW dans ce +tutoriel, mais nous verrons tout cela dans le prochain chapitre. + +Nous avons cependant encore deux composants à évoquer pour afficher quelque chose : une Surface (`VkSurfaceKHR`) et une +Swap Chain (`VkSwapchainKHR`). Remarquez le suffixe «KHR», qui indique que ces fonctionnalités font partie d’une +extension. L'API est elle-même totalement agnostique de la plateforme sur laquelle elle travaille, nous devons donc +utiliser l’extension standard WSI (Window System Interface) pour interagir avec le gestionnaire de fenêtre. La +Surface est une abstraction cross-platform de la fenêtre, et est généralement créée en fournissant une référence à +une fenêtre spécifique à la plateforme, par exemple «HWND» sur Windows. Heureusement pour nous, la librairie GLFW +possède une fonction permettant de gérer tous les détails spécifiques à la plateforme pour nous. + +La swap chain est une collection de cibles sur lesquelles nous pouvons effectuer un rendu. Son but principal est +d’assurer que l’image sur laquelle nous travaillons n’est pas celle utilisée par l’écran. Nous sommes ainsi sûrs que +l’image affichée est complète. Chaque fois que nous voudrons afficher une image nous devrons demander à la swap chain de +nous fournir une cible disponible. Une fois le traitement de la cible terminé, nous la rendrons à la swap chain qui +l’utilisera en temps voulu pour l’affichage à l’écran. Le nombre de cibles et les conditions de leur affichage dépend +du mode utilisé lors du paramétrage de la Swap Chain. Ceux-ci peuvent être le double buffering (synchronisation +verticale) ou le triple buffering. Nous détaillerons tout cela dans le chapitre dédié à la Swap Chain. + +Certaines plateformes permettent d'effectuer un rendu directement à l'écran sans passer par un gestionnaire de fenêtre, +et ce en vous donnant la possibilité de créer une surface qui fait la taille de l'écran. Vous pouvez alors par exemple +créer votre propre gestionnaire de fenêtre. + +### Étape 4 - Image views et framebuffers + +Pour dessiner sur une image originaire de la swap chain, nous devons l'encapsuler dans une `VkImageView` et un +`VkFramebuffer`. Une vue sur une image correspond à une certaine partie de l’image utilisée, et un framebuffer +référence plusieurs vues pour les traiter comme des cible de couleur, de profondeur ou de stencil. Dans la mesure où +il peut y avoir de nombreuses images dans la swap chain, nous créerons en amont les vues et les framebuffers pour +chacune d’entre elles, puis sélectionnerons celle qui nous convient au moment de l’affichage. + +### Étape 5 - Render passes + +Avec Vulkan, une render pass décrit les types d’images utilisées lors du rendu, comment elles sont utilisées et +comment leur contenu doit être traité. Pour notre affichage d’un triangle, nous dirons à Vulkan que nous utilisons une +seule image pour la couleur et que nous voulons qu’elle soit préparée avant l’affichage en la remplissant d’une couleur +opaque. Là où la passe décrit le type d’images utilisées, un framebuffer sert à lier les emplacements utilisés par la +passe à une image complète. + +### Étape 6 - Le pipeline graphique + +Le pipeline graphique est configuré lors de la création d’un `VkPipeline`. Il décrit les éléments paramétrables de la +carte graphique, comme les opérations réalisées par le depth buffer (gestion de la profondeur), et les étapes +programmables à l’aide de `VkShaderModules`. Ces derniers sont créés à partir de byte code. Le driver doit également +être informé des cibles du rendu utilisées dans le pipeline, ce que nous lui disons en référençant la render pass. + +L’une des particularités les plus importantes de Vulkan est que la quasi totalité de la configuration des étapes doit +être réalisée à l’avance. Cela implique que si vous voulez changer un shader ou la conformation des sommets, la +totalité du pipeline doit être recréée. Vous aurez donc probablement de nombreux `VkPipeline` correspondant à toutes +les combinaisons dont votre programme aura besoin. Seules quelques configurations basiques peuvent être changées de +manière dynamique, comme la couleur de fond. Les états doivent aussi être anticipés : il n’y a par exemple pas de +fonction de blending par défaut. + +La bonne nouvelle est que grâce à cette anticipation, ce qui équivaut à peu près à une compilation versus une +interprétation, il y a beaucoup plus d’optimisations possibles pour le driver et le temps d’exécution est plus +prévisible, car les grandes étapes telles le changement de pipeline sont faites très explicites. + +### Étape 7 - Command pools et command buffers + +Comme dit plus haut, nombre d’opérations comme le rendu doivent être transmise à une queue. Ces opérations doivent +d’abord être enregistrées dans un `VkCommandBuffer` avant d’être envoyées. Ces command buffers sont alloués à partir +d’une «`VkCommandPool`» spécifique à une queue family. Pour afficher notre simple triangle nous devrons enregistrer les +opérations suivantes : + +* Lancer la render pass +* Lier le pipeline graphique +* Afficher 3 sommets +* Terminer la passe + +Du fait que l’image que nous avons extraite du framebuffer pour nous en servir comme cible dépend de l’image que la swap +chain nous fournira, nous devons préparer un command buffer pour chaque image possible et choisir le bon au moment de +l’affichage. Nous pourrions en créer un à chaque frame mais ce ne serait pas aussi efficace. + +### Étape 8 - Boucle principale + +Maintenant que nous avons inscrit les commandes graphiques dans des command buffers, la boucle principale n’est plus +qu'une question d’appels. Nous acquérons d’abord une image de la swap chain en utilisant `vkAcquireNextImageKHR`. Nous +sélectionnons ensuite le command buffer approprié pour cette image et le postons à la queue avec `vkQueueSubmit`. Enfin, +nous retournons l’image à la swap chain pour sa présentation à l’écran à l’aide de `vkQueuePresentKHR`. + +Les opérations envoyées à la queue sont exécutées de manière asynchrone. Nous devons donc utiliser des objets de +synchronisation tels que des sémaphores pour nous assurer que les opérations sont exécutées dans l’ordre voulu. +L’exécution du command buffer d’affichage doit de plus attendre que l’acquisition de l’image soit terminée, sinon nous +pourrions dessiner sur une image utilisée pour l’affichage. L’appel à `vkQueuePresentKHR` doit aussi attendre que +l’affichage soit terminé. + +### Résumé + +Ce tour devrait vous donner une compréhension basique du travail que nous aurons à fournir pour afficher notre premier +triangle. Un véritable programme contient plus d’étapes comme allouer des vertex Buffers, créer des Uniform Buffers et +envoyer des textures, mais nous verrons cela dans des chapitres suivants. Nous allons commencer par les bases car Vulkan +a suffisamment d’étapes ainsi. Notez que nous allons "tricher" en écrivant les coordonnées du triangle directement dans +un shader, afin d’éviter l’utilisation d’un vertex buffer qui nécessite une certaine familiarité avec les Command +Buffers. + +En résumé nous devrons, pour afficher un triangle : + +* Créer une `VkInstance` +* Sélectionner une carte graphique compatible (`VkPhysicalDevice`) +* Créer un `VkDevice` et une `VkQueue` pour l’affichage et la présentation +* Créer une fenêtre, une surface dans cette fenêtre et une swap chain +* Considérer les images de la swap chain comme des `VkImageViews` puis des `VkFramebuffers` +* Créer la render pass spécifiant les cibles d’affichage et leurs usages +* Créer des framebuffers pour ces passes +* Générer le pipeline graphique +* Allouer et enregistrer des Command Buffers contenant toutes les commandes pour toutes les images de la swap chain +* Dessiner sur les frames en acquérant une image, en soumettant la commande d’affichage correspondante et en retournant +l’image à la swap chain + +Cela fait beaucoup d’étapes, cependant le but de chacune d’entre elles sera explicitée clairement et simplement dans les +chapitres suivants. Si vous êtes confus quant à l’intérêt d’une étape dans le programme entier, référez-vous à ce +premier chapitre. + +## Concepts de l’API + +Ce chapitre va conclure en survolant la structure de l’API à un plus bas niveau. + +### Conventions + +Toute les fonctions, les énumérations et les structures de Vulkan sont définies dans le header `vulkan.h`, inclus dans +le [SDK Vulkan](https://lunarg.com/vulkan-sdk/) développé par LunarG. Nous verrons comment l’installer dans le prochain +chapitre. + +Les fonctions sont préfixées par ‘vk’, les types comme les énumération et les structures par ‘Vk’ et les macros par +‘VK_’. L’API utilise massivement les structures pour la création d’objet plutôt que de passer des arguments à des +fonctions. Par exemple la création d’objet suit généralement le schéma suivant : + +```c++ +VkXXXCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO; +createInfo.pNext = nullptr; +createInfo.foo = ...; +createInfo.bar = ...; + +VkXXX object; +if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) { + std::cerr << "failed to create object" << std::endl; + return false; +} +``` + +De nombreuses structure imposent que l’on spécifie explicitement leur type dans le membre donnée «sType». Le membre +donnée «pNext» peut pointer vers une extension et sera toujours `nullptr` dans ce tutoriel. Les fonctions qui créent ou +détruisent les objets ont un paramètre appelé `VkAllocationCallbacks`, qui vous permettent de spécifier un allocateur. +Nous le mettrons également à `nullptr`. + +La plupart des fonctions retournent un `VkResult`, qui peut être soit `VK_SUCCESS` soit un code d’erreur. La +spécification décrit lesquels chaque fonction renvoie et ce qu’ils signifient. + +### Validation layers + +Vulkan est pensé pour la performance et pour un travail minimal pour le driver. Il inclue donc très peu de gestion +d’erreur et de système de débogage. Le driver crashera beaucoup plus souvent qu’il ne retournera de code d’erreur si +vous faites quelque chose d’inattendu. Pire, il peut fonctionner sur votre carte graphique mais pas sur une autre. + +Cependant, Vulkan vous permet d’effectuer des vérifications précises de chaque élément à l’aide d’une fonctionnalité +nommée «validation layers». Ces layers consistent en du code s’insérant entre l’API et le driver, et permettent de +lancer des analyses de mémoire et de relever les défauts. Vous pouvez les activer pendant le développement et les +désactiver sans conséquence sur la performance. N’importe qui peut écrire ses validation layers, mais celui du SDK de +LunarG est largement suffisant pour ce tutoriel. Vous aurez cependant à écrire vos propres fonctions de callback pour +le traitement des erreurs émises par les layers. + +Du fait que Vulkan soit si explicite pour chaque opération et grâce à l’extensivité des validations layers, trouver les +causes de l’écran noir peut en fait être plus simple qu’avec OpenGL ou Direct3D! + +Il reste une dernière étape avant de commencer à coder : mettre en place +[l’environnement de développement](!fr/Environnement_de_développement). diff --git "a/fr/02_Environnement_de_d\303\251veloppement.md" "b/fr/02_Environnement_de_d\303\251veloppement.md" new file mode 100644 index 00000000..1041fdbe --- /dev/null +++ "b/fr/02_Environnement_de_d\303\251veloppement.md" @@ -0,0 +1,520 @@ +Dans ce chapitre nous allons paramétrer votre environnement de développement pour Vulkan et installer des librairies +utiles. Tous les outils que nous allons utiliser, excepté le compilateur, seront compatibles Windows, Linux et MacOS. +Cependant les étapes pour les installer diffèrent un peu, d'où les sections suivantes. + +## Windows + +Si vous développez pour Windows, je partirai du principe que vous utilisez Visual Studio pour ce projet. +Pour un support complet de C++17, il vous faut Visual Studio 2017 or 2019. Les étapes décrites ci-dessous +ont été écrites pour VS 2017. + +### SDK Vulkan + +Le composant central du développement d'applications Vulkan est le SDK. Il inclut les headers, les validation layers +standards, des outils de débogage et un loader pour les fonctions Vulkan. Ce loader récupère les fonctions dans le +driver à l'exécution, comme GLEW pour OpenGL - si cela vous parle. + +Le SDK peut être téléchargé sur [le site de LunarG](https://vulkan.lunarg.com/) en utilisant les boutons en bas de page. +Vous n'avez pas besoin de compte, mais celui-ci vous donne accès à une documentation supplémentaire qui pourra vous être +utile. + +![](/images/vulkan_sdk_download_buttons.png) + +Réalisez l'installation et notez l'emplacement du SDK. La première chose que nous allons faire est vérifier que votre +carte graphique supporte Vulkan. Allez dans le dossier d'installation du SDK, ouvrez le dossier "Bin" et lancez +"vkcube.exe". Vous devriez voire la fenêtre suivante : + +![](/images/cube_demo.png) + +Si vous recevez un message d'erreur assurez-vous que votre driver est à jour, inclut Vulkan et que votre carte graphique +est supportée. Référez-vous au [chapitre introductif](!fr/Introduction) pour les liens vers les principaux constructeurs. + +Il y a d'autres programmes dans ce dossier qui vous seront utiles : "glslangValidator.exe" et "glslc.exe". Nous en aurons besoin pour la +compilation des shaders. Ils transforment un code compréhensible facilement et semblable au C (le +[GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language)) en bytecode. +Nous couvrirons cela dans le chapitre des [modules shader](!fr/Dessiner_un_triangle/Pipeline_graphique_basique/Modules_shaders). +Le dossier "Bin" contient aussi les fichiers binaires du loader Vulkan et des validation layers. Le dossier "Lib" en +contient les librairies. + +Enfin, le dossier "Include" contient les headers Vulkan. Vous pouvez parourir les autres +fichiers, mais nous ne les utiliserons pas dans ce tutoriel. + +### GLFW + +Comme dit précédemment, Vulkan ignore la plateforme sur laquelle il opère, et n'inclut pas d'outil de création +de fenêtre où afficher les résultats de notre travail. Pour bien exploiter les possibilités cross-platform de Vulkan +et éviter les horreurs de Win32, nous utiliserons la [librairie GLFW](http://www.glfw.org/) pour créer une fenêtre et ce +sur Windows, Linux ou MacOS. Il existe d'autres librairies telles que [SDL](https://www.libsdl.org/), mais GLFW a +l'avantage d'abstraire d'autres aspects spécifiques à la plateforme requis par Vulkan. + +Vous pouvez trouver la dernière version de GLFW sur leur site officiel. Nous utiliserons la version 64 bits, mais vous +pouvez également utiliser la version 32 bits. Dans ce cas assurez-vous de bien lier le dossier "Lib32" dans le SDK et +non "Lib". Après avoir téléchargé GLFW, extrayez l'archive à l'emplacement qui vous convient. J'ai choisi de créer un +dossier "Librairies" dans le dossier de Visual Studio. + +![](/images/glfw_directory.png) + +### GLM + +Contrairement à DirectX 12, Vulkan n'intègre pas de librairie pour l'algèbre linéaire. Nous devons donc en télécharger +une. [GLM](http://glm.g-truc.net/) est une bonne librairie conçue pour être utilisée avec les APIs graphiques, et est +souvent utilisée avec OpenGL. + +GLM est une librairie écrite exclusivement dans les headers, il suffit donc d'en télécharger la +[dernière version](https://github.com/g-truc/glm/releases), la stocker où vous le souhaitez et l'inclure là où vous en +aurez besoin. Vous devrez vous trouver avec quelque chose de semblable : + +![](/images/library_directory.png) + +### Préparer Visual Studio + +Maintenant que vous avez installé toutes les dépendances, nous pouvons préparer un projet Visual Studio pour Vulkan, +et écrire un peu de code pour vérifier que tout fonctionne. + +Lancez Visual Studio et créez un nouveau projet "Windows Desktop Wizard", entrez un nom et appuyez sur OK. + +![](/images/vs_new_cpp_project.png) + +Assurez-vous que "Console Application (.exe)" est séléctionné pour le type d'application afin que nous ayons un endroit +où afficher nos messages d'erreur, et cochez "Empty Project" afin que Visual Studio ne génère pas un code de base. + +![](/images/vs_application_settings.png) + +Appuyez sur OK pour créer le projet et ajoutez un fichier source C++. Vous devriez déjà savoir faire ça, mais les étapes +sont tout de même incluses ici. + +![](/images/vs_new_item.png) + +![](/images/vs_new_source_file.png) + +Ajoutez maintenant le code suivant à votre fichier. Ne cherchez pas à en comprendre les tenants et aboutissants, il sert +juste à s'assurer que tout compile correctement et qu'une application Vulkan fonctionne. Nous recommencerons tout depuis +le début dès le chapitre suivant. + +```c++ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include + +int main() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan window", nullptr, nullptr); + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + + std::cout << extensionCount << " extensions supported\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Configurons maintenant le projet afin de se débarrasser des erreurs. Ouvrez le dialogue des propriétés du projet et +assurez-vous que "All Configurations" est sélectionné, car la plupart des paramètres s'appliquent autant à "Debug" +qu'à "Release". + +![](/images/vs_open_project_properties.png) + +![](/images/vs_all_configs.png) + +Allez à "C++" -> "General" -> "Additional Include Directories" et appuyez sur "" dans le menu déroulant. + +![](/images/vs_cpp_general.png) + +Ajoutez les dossiers pour les headers Vulkan, GLFW et GLM : + +![](/images/vs_include_dirs.png) + +Ensuite, ouvrez l'éditeur pour les dossiers des librairies sous "Linker" -> "General" : + +![](/images/vs_link_settings.png) + +Et ajoutez les emplacements des fichiers objets pour Vulkan et GLFW : + +![](/images/vs_link_dirs.png) + +Allez à "Linker" -> "Input" et appuyez sur "" dans le menu déroulant "Additional Dependencies" : + +![](/images/vs_link_input.png) + +Entrez les noms des fichiers objets GLFW et Vulkan : + +![](/images/vs_dependencies.png) + +Vous pouvez enfin fermer le dialogue des propriétés. Si vous avez tout fait correctement vous ne devriez plus voir +d'erreur dans votre code. + +Assurez-vous finalement que vous compilez effectivement en 64 bits : + +![](/images/vs_build_mode.png) + +Appuyez sur F5 pour compiler et lancer le projet. Vous devriez voir une fenêtre s'afficher comme cela : + +![](/images/vs_test_window.png) + +Si le nombre d'extensions est nul, il y a un problème avec la configuration de Vulkan sur votre système. Sinon, vous +êtes fin prêts à vous [lancer avec Vulkan!](!fr/Dessiner_un_triangle/Mise_en_place/Code_de_base) + +## Linux + +Ces instructions sont conçues pour les utilisateurs d'Ubuntu et Fedora, mais vous devriez pouvoir suivre ces instructions depuis +une autre distribution si vous adaptez les commandes "apt" ou "dnf" à votre propre gestionnaire de +packages. Il vous faut un compilateur qui supporte C++17 (GCC 7+ ou Clang 5+). Vous aurez également besoin de make. + +### Paquets Vulkan + +Les composants les plus importants pour le développement d'applications Vulkan sous Linux sont le loader Vulkan, les validation layers et quelques utilitaires pour tester que votre machine est bien en état de faire fonctionner une application Vulkan: +* `sudo apt install vulkan-tools` ou `sudo dnf install vulkan-tools`: Les utilitaires en ligne de commande, plus précisément `vulkaninfo` et `vkcube`. Lancez ceux-ci pour vérifier le bon fonctionnement de votre machine pour Vulkan. +* `sudo apt install libvulkan-dev` ou `sudo dnf install vulkan-headers vulkan-loader-devel`: Installe le loader Vulkan. Il sert à aller chercher les fonctions auprès du driver de votre GPU au runtime, de la même façon que GLEW le fait pour OpenGL - si vous êtes familier avec ceci. +* `sudo apt install vulkan-validationlayers-dev` ou `sudo dnf install mesa-vulkan-drivers vulkan-validation-layers-devel`: Installe les layers de validation standards. Ceux-ci sont cruciaux pour débugger vos applications Vulkan, et nous en reparlerons dans un prochain chapitre. + +Si l'installation est un succès, vous devriez être prêt pour la partie Vulkan. N'oubliez pas de lancer `vkcube` et assurez-vous de voir la fenêtre suivante: + +![](/images/cube_demo_nowindow.png) + +### GLFW + +Comme dit précédemment, Vulkan ignore la plateforme sur laquelle il opère, et n'inclut pas d'outil de création +de fenêtre où afficher les résultats de notre travail. Pour bien exploiter les possibilités cross-platform de +Vulkan, nous utiliserons la [librairie GLFW](http://www.glfw.org/) pour créer une fenêtre sur Windows, Linux +ou MacOS indifféremment. Il existe d'autres librairies telles que [SDL](https://www.libsdl.org/), mais GLFW à +l'avantage d'abstraire d'autres aspects spécifiques à la plateforme requis par Vulkan. + +Nous allons installer GLFW à l'aide de la commande suivante: +```bash +sudo apt install libglfw3-dev +``` +ou +```bash +sudo dnf install glfw-devel +``` + +### GLM + +Contrairement à DirectX 12, Vulkan n'intègre pas de librairie pour l'algèbre linéaire. Nous devons donc en télécharger +une. [GLM](http://glm.g-truc.net/) est une bonne librairie conçue pour être utilisée avec les APIs graphiques, et est +souvent utilisée avec OpenGL. + +Cette librairie contenue intégralement dans les headers peut être installée depuis le package "libglm-dev" ou +"glm-devel" : + +```bash +sudo apt install libglm-dev +``` +ou +```bash +sudo dnf install glm-devel +``` + +### Compilateur de shader + +Nous avons tout ce qu'il nous faut, excepté un programme qui compile le code [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) lisible par un humain en bytecode. + +Deux compilateurs de shader populaires sont `glslangValidator` de Khronos et `glslc` de Google. Ce dernier a l'avantage d'être proche de GCC et Clang à l'usage,. +Pour cette raison, nous l'utiliserons: Ubuntu, téléchargez les exécutables [non officiels](https://github.com/google/shaderc/blob/main/downloads.md) et copiez `glslc` dans votre répertoire `/usr/local/bin`. Notez que vous aurez certainement besoin d'utiliser `sudo` en fonctions de vos permissions. Fedora, utilise `sudo dnf install glslc`. +Pour tester, lancez `glslc` depuis le répertoire de votre choix et il devrait se plaindre qu'il n'a reçu aucun shader à compiler de votre part: + +`glslc: error: no input files` + +Nous couvrirons l'usage de `glslc` plus en détails dans le chapitre des [modules shaders](!fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/01_Modules_shaders.md) + +### Préparation d'un fichier makefile + +Maintenant que vous avez installé toutes les dépendances, nous pouvons préparer un makefile basique pour Vulkan et +écrire un code très simple pour s'assurer que tout fonctionne correctement. + +Ajoutez maintenant le code suivant à votre fichier. Ne cherchez pas à en comprendre les tenants et aboutissants, il sert +juste à s'assurer que tout compile correctement et qu'une application Vulkan fonctionne. Nous recommencerons tout depuis +le début dès le chapitre suivant. + +```c++ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include + +int main() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan window", nullptr, nullptr); + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + + std::cout << extensionCount << " extensions supported\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Nous allons maintenant créer un makefile pour compiler et lancer ce code. Créez un fichier "Makefile". Je pars du +principe que vous connaissez déjà les bases de makefile, dont les variables et les règles. Sinon vous pouvez trouver des +introductions claires sur internet, par exemple [ici](https://makefiletutorial.com/). + +Nous allons d'abord définir quelques variables pour simplifier le reste du fichier. +Définissez `CFLAGS`, qui spécifiera les arguments pour la compilation : + +```make +CFLAGS = -std=c++17 -O2 +``` + +Nous utiliserons du C++ moderne (`-std=c++17`) et compilerons avec le paramètre d'optimisation `-O2`. Vous pouvez le retirer pour compiler nos programmes plus rapidement, mais n'oubliez pas de le remettre pour compiler des exécutables prêts à être distribués. + +Définissez de manière analogue `LDFLAGS` : + +```make +LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi +``` + +Le premier flag correspond à GLFW, `-lvulkan` correspond au loader dynamique des fonctions Vulkan. Le reste des options correspondent à des bibliothèques systèmes liés à la gestion des fenêtres et aux threads nécessaire pour le bon fonctionnement de GLFW. + +Spécifier les commandes pour la compilation de "VulkanTest" est désormais un jeu d'enfant. Assurez-vous que vous +utilisez des tabulations et non des espaces pour l'indentation. + +```make +VulkanTest: main.cpp + g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS) +``` + +Vérifiez que le fichier fonctionne en le sauvegardant et en exécutant make depuis un terminal ouvert dans le +dossier le contenant. Vous devriez avoir un exécutable appelé "VulkanTest". + +Nous allons ensuite définir deux règles, `test` et `clean`. La première exécutera le programme et le second supprimera +l'exécutable : + +```make +.PHONY: test clean + +test: VulkanTest + ./VulkanTest + +clean: + rm -f VulkanTest +``` + +Lancer `make test` doit vous afficher le programme sans erreur, listant le nombre d'extensions disponible pour Vulkan. +L'application devrait retourner le code de retour 0 (succès) quand vous fermez la fenêtre vide. +Vous devriez désormais avoir un makefile ressemblant à ceci : + +```make +CFLAGS = -std=c++17 -O2 +LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi + +VulkanTest: main.cpp + g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS) + +.PHONY: test clean + +test: VulkanTest + ./VulkanTest + +clean: + rm -f VulkanTest +``` + +Vous pouvez désormais utiliser ce dossier comme exemple pour vos futurs projets Vulkan. +Faites-en une copie, changez le nom du projet pour quelque chose comme `HelloTriangle` et retirez tout le code contenu dans `main.cpp`. + +Bravo, vous êtes fin prêts à vous [lancer avec Vulkan!](!fr/Dessiner_un_triangle/Mise_en_place/Code_de_base) + +## MacOS + +Ces instructions partent du principe que vous utilisez Xcode et le +[gestionnaire de packages Homebrew](https://brew.sh/). Vous aurez besoin de MacOS 10.11 minimum, et votre ordinateur +doit supporter l'[API Metal](https://en.wikipedia.org/wiki/Metal_(API)#Supported_GPUs). + +### Le SDK Vulkan + +Le SDK est le composant le plus important pour programmer une application avec Vulkan. Il inclue headers, validations +layers, outils de débogage et un loader dynamique pour les fonctions Vulkan. Le loader cherche les fonctions dans le +driver pendant l'exécution, comme GLEW pour OpenGL, si cela vous parle. + +Le SDK se télécharge sur le [site de LunarG](https://vulkan.lunarg.com/) en utilisant les boutons en bas de page. Vous +n'avez pas besoin de créer de compte, mais il permet d'accéder à une documentation supplémentaire qui pourra vous être +utile. + +![](/images/vulkan_sdk_download_buttons.png) + +La version MacOS du SDK utilise [MoltenVK](https://moltengl.com/). Il n'y a pas de support natif pour Vulkan sur MacOS, +donc nous avons besoin de MoltenVK pour transcrire les appels à l'API Vulkan en appels au framework Metal d'Apple. +Vous pouvez ainsi exploiter pleinement les possibilités de cet API automatiquement. + + +Une fois téléchargé, extrayez-en le contenu où vous le souhaitez. Dans le dossier extrait, il devrait y avoir un +sous-dossier "Applications" comportant des exécutables lançant des démos du SDK. Lancez "vkcube" pour vérifier que vous +obtenez ceci : + +![](/images/cube_demo_mac.png) + +### GLFW + +Comme dit précédemment, Vulkan ignore la plateforme sur laquelle il opère, et n'inclut pas d'outil de création +de fenêtre où afficher les résultats de notre travail. Pour bien exploiter les possibilités cross-platform de +Vulkan, nous utiliserons la [librairie GLFW](http://www.glfw.org/) pour créer une fenêtre qui supportera Windows, Linux +et MacOS. Il existe d'autres librairies telles que [SDL](https://www.libsdl.org/), mais GLFW à l'avantage d'abstraire +d'autres aspects spécifiques à la plateforme requis par Vulkan. + +Nous utiliserons le gestionnaire de package Homebrew pour installer GLFW. Le support Vulkan sur MacOS n'étant pas +parfaitement disponible (à l'écriture du moins) sur la version 3.2.1, nous installerons le package "glfw3" ainsi : + +```bash +brew install glfw3 --HEAD +``` + +### GLM + +Vulkan n'inclut aucune libraire pour l'algèbre linéaire, nous devons donc en télécharger une. +[GLM](http://glm.g-truc.net/) est une bonne librairie souvent utilisée avec les APIs graphiques dont OpenGL. + +Cette librairie est intégralement codée dans les headers et se télécharge avec le package "glm" : + +```bash +brew install glm +``` + +### Préparation de Xcode + +Maintenant que nous avons toutes les dépendances nous pouvons créer dans Xcode un projet Vulkan basique. La plupart +des opérations seront de la "tuyauterie" pour lier les dépendances au projet. Notez que vous devrez remplacer toutes les +mentions "vulkansdk" par le dossier où vous avez extrait le SDK Vulkan. + +Lancez Xcode et créez un nouveau projet. Sur la fenêtre qui s'ouvre sélectionnez Application > Command Line Tool. + +![](/images/xcode_new_project.png) + +Sélectionnez "Next", inscrivez un nom de projet et choisissez "C++" pour "Language". + +![](/images/xcode_new_project_2.png) + +Appuyez sur "Next" et le projet devrait être créé. Copiez le code suivant à la place du code généré dans le fichier +"main.cpp" : + +```c++ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include + +int main() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan window", nullptr, nullptr); + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + + std::cout << extensionCount << " extensions supported\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Gardez à l'esprit que vous n'avez pas à comprendre tout ce que le code fait, dans la mesure où il se contente +d'appeler quelques fonctions de l'API pour s'assurer que tout fonctionne. Nous verrons toutes ces fonctions en détail +plus tard. + +Xcode devrait déjà vous afficher des erreurs comme le fait que des librairies soient introuvables. Nous allons +maintenant les faire disparaître. Sélectionnez votre projet sur le menu *Project Navigator*. Ouvrez +*Build Settings* puis : + +* Trouvez le champ **Header Search Paths** et ajoutez "/usr/local/include" (c'est ici que Homebrew installe les headers) +et "vulkansdk/macOS/include" pour le SDK. +* Trouvez le champ **Library Search Paths** et ajoutez "/usr/local/lib" (même raison pour les librairies) et +"vulkansdk/macOS/lib". + +Vous avez normalement (avec des différences évidentes selon l'endroit où vous avez placé votre SDK) : + +![](/images/xcode_paths.png) + +Maintenant, dans le menu *Build Phases*, ajoutez les frameworks "glfw3" et "vulkan" dans **Link Binary With +Librairies**. Pour nous simplifier les choses, nous allons ajouter les librairies dynamiques directement dans le projet +(référez-vous à la documentation de ces librairies si vous voulez les lier de manière statique). + +* Pour glfw ouvrez le dossier "/usr/local/lib" où vous trouverez un fichier avec un nom comme "libglfw.3.x.dylib" où x +est le numéro de la version. Glissez ce fichier jusqu'à la barre des "Linked Frameworks and Librairies" dans Xcode. +* Pour Vulkan, rendez-vous dans "vulkansdk/macOS/lib" et répétez l'opération pour "libvulkan.1.dylib" et "libvulkan.1.x.xx +.dylib" avec les x correspondant à la version du SDK que vous avez téléchargé. + +Maintenant que vous avez ajouté ces librairies, remplissez le champ `Destination` avec "Frameworks" dans **Copy Files**, +supprimez le sous-chemin et décochez "Copy only when installing". Cliquez sur le "+" et ajoutez-y les trois mêmes +frameworks. + +Votre configuration Xcode devrait ressembler à cela : + +![](/images/xcode_frameworks.png) + +Il ne reste plus qu'à définir quelques variables d'environnement. Sur la barre d'outils de Xcode allez à `Product` > +`Scheme` > `Edit Scheme...`, et dans la liste `Arguments` ajoutez les deux variables suivantes : + +* VK_ICD_FILENAMES = `vulkansdk/macOS/share/vulkan/icd.d/MoltenVK_icd.json` +* VK_LAYER_PATH = `vulkansdk/macOS/share/vulkan/explicit_layer.d` + +Vous avez normalement ceci : + +![](/images/xcode_variables.png) + +Vous êtes maintenant prêts! Si vous lancez le projet (en pensant à bien choisir Debug ou Release) vous devrez +avoir ceci : + +![](/images/xcode_output.png) + +Si vous obtenez `0 extensions supported`, il y a un problème avec la configuration de Vulkan sur votre système. Les +autres données proviennent de librairies, et dépendent de votre configuration. + +Vous êtes maintenant prêts à vous [lancer avec Vulkan!](!fr/Dessiner_un_triangle/Mise_en_place/Code_de_base). diff --git a/fr/03_Dessiner_un_triangle/00_Mise_en_place/00_Code_de_base.md b/fr/03_Dessiner_un_triangle/00_Mise_en_place/00_Code_de_base.md new file mode 100644 index 00000000..4411fa7b --- /dev/null +++ b/fr/03_Dessiner_un_triangle/00_Mise_en_place/00_Code_de_base.md @@ -0,0 +1,200 @@ +## Structure générale + +Dans le chapitre précédent nous avons créé un projet Vulkan avec une configuration solide et nous l'avons testé. Nous +recommençons ici à partir du code suivant : + +```c++ +#include + +#include +#include +#include +#include + +class HelloTriangleApplication { +public: + void run() { + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + void initVulkan() { + + } + + void mainLoop() { + + } + + void cleanup() { + + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl;` + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} +``` + +Nous incluons d'abord le header Vulkan du SDK, qui fournit les fonctions, les structures et les énumérations. +`` et `` nous permettront de reporter et de traiter les erreurs. Le header `` nous +servira pour l'écriture d'une lambda dans la section sur la gestion des ressources. `` nous fournit les macros +`EXIT_FAILURE` et `EXIT_SUCCESS` (optionnelles). + +Le programme est écrit à l'intérieur d'une classe, dans laquelle seront stockés les objets Vulkan. Nous avons également +une fonction pour la création de chacun de ces objets. Une fois toute l'initialisation réalisée, nous entrons dans la +boucle principale, qui attend que nous fermions la fenêtre pour quitter le programme, après avoir libéré grâce à la +fonction cleanup toutes les ressources que nous avons allouées . + +Si nous rencontrons une quelconque erreur lors de l'exécution nous lèverons une `std::runtime_error` comportant un +message descriptif, qui sera affiché sur le terminal depuis la fonction `main`. Afin de s'assurer que nous récupérons +bien toutes les erreurs, nous utilisons `std::exception` dans le `catch`. Nous verrons bientôt que la requête de +certaines extensions peut mener à lever des exceptions. + +À peu près tous les chapitres à partir de celui-ci introduiront une nouvelle fonction appelée dans `initVulkan` et un +nouvel objet Vulkan qui sera justement créé par cette fonction. Il sera soit détruit dans `cleanup`, soit libéré +automatiquement. + +## Gestion des ressources + +De la même façon qu'une quelconque ressource explicitement allouée par `new` doit être explicitement libérée par `delete`, nous +devrons explicitement détruire quasiment toutes les ressources Vulkan que nous allouerons. Il est possible d'exploiter +des fonctionnalités du C++ pour s’acquitter automatiquement de cela. Ces possibilités sont localisées dans `` si +vous désirez les utiliser. Cependant nous resterons explicites pour toutes les opérations dans ce tutoriel, car la +puissance de Vulkan réside en particulier dans la clareté de l'expression de la volonté du programmeur. De plus, cela +nous permettra de bien comprendre la durée de vie de chacun des objets. + +Après avoir suivi ce tutoriel vous pourrez parfaitement implémenter une gestion automatique des ressources en +spécialisant `std::shared_ptr` par exemple. L'utilisation du [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) +à votre avantage est toujours recommandé en C++ pour de gros programmes Vulkan, mais il est quand même bon de +commencer par connaître les détails de l'implémentation. + +Les objets Vulkan peuvent être créés de deux manières. Soit ils sont directement créés avec une fonction du type +`vkCreateXXX`, soit ils sont alloués à l'aide d'un autre objet avec une fonction `vkAllocateXXX`. Après vous +être assuré qu'il n'est plus utilisé où que ce soit, il faut le détruire en utilisant les fonctions +`vkDestroyXXX` ou `vkFreeXXX`, respectivement. Les paramètres de ces fonctions varient sauf pour l'un d'entre eux : +`pAllocator`. Ce paramètre optionnel vous permet de spécifier un callback sur un allocateur de mémoire. Nous +n'utiliserons jamais ce paramètre et indiquerons donc toujours `nullptr`. + +## Intégrer GLFW + +Vulkan marche très bien sans fenêtre si vous voulez l'utiliser pour du rendu sans écran (offscreen rendering en +Anglais), mais c'est tout de même plus intéressant d'afficher quelque chose! Remplacez d'abord la ligne +`#include ` par : + +```c++ +#define GLFW_INCLUDE_VULKAN +#include +``` + +GLFW va alors automatiquement inclure ses propres définitions des fonctions Vulkan et vous fournir le header Vulkan. +Ajoutez une fonction `initWindow` et appelez-la depuis `run` avant les autres appels. Nous utiliserons cette fonction +pour initialiser GLFW et créer une fenêtre. + +```c++ +void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); +} + +private: + void initWindow() { + + } +``` + +Le premier appel dans `initWindow` doit être `glfwInit()`, ce qui initialise la librairie. Dans la mesure où GLFW a été +créée pour fonctionner avec OpenGL, nous devons lui demander de ne pas créer de contexte OpenGL avec l'appel suivant : + +```c++ +glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); +``` + +Dans la mesure où redimensionner une fenêtre n'est pas chose aisée avec Vulkan, nous verrons cela plus tard et +l'interdisons pour l'instant. + +```c++ +glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); +``` + +Il ne nous reste plus qu'à créer la fenêtre. Ajoutez un membre privé `GLFWWindow* m_window` pour en stocker une +référence, et initialisez la ainsi : + +```c++ +window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); +``` + +Les trois premiers paramètres indiquent respectivement la largeur, la hauteur et le titre de la fenêtre. Le quatrième +vous permet optionnellement de spécifier un moniteur sur lequel ouvrir la fenêtre, et le cinquième est spécifique à +OpenGL. + +Nous devrions plutôt utiliser des constantes pour la hauteur et la largeur dans la mesure où nous aurons besoin de ces +valeurs dans le futur. J'ai donc ajouté ceci au-dessus de la définition de la classe `HelloTriangleApplication` : + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; +``` + +et remplacé la création de la fenêtre par : + +```c++ +window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +``` + +Vous avez maintenant une fonction `initWindow` ressemblant à ceci : + +```c++ +void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +} +``` + +Pour s'assurer que l'application tourne jusqu'à ce qu'une erreur ou un clic sur la croix ne l'interrompe, nous +devons écrire une petite boucle de gestion d'évènements : + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } +} +``` + +Ce code est relativement simple. GLFW récupère tous les évènements disponibles, puis vérifie qu'aucun d'entre eux ne +correspond à une demande de fermeture de fenêtre. Ce sera aussi ici que nous appellerons la fonction qui affichera un +triangle. + +Une fois la requête pour la fermeture de la fenêtre récupérée, nous devons détruire toutes les ressources allouées et +quitter GLFW. Voici notre première version de la fonction `cleanup` : + +```c++ +void cleanup() { + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +Si vous lancez l'application, vous devriez voir une fenêtre appelée "Vulkan" qui se ferme en cliquant sur la croix. +Maintenant que nous avons une base pour notre application Vulkan, [créons notre premier objet Vulkan!](!fr/Dessiner_un_triangle/Mise_en_place/Instance)! + +[Code C++](/code/00_base_code.cpp) diff --git a/fr/03_Dessiner_un_triangle/00_Mise_en_place/01_Instance.md b/fr/03_Dessiner_un_triangle/00_Mise_en_place/01_Instance.md new file mode 100644 index 00000000..851c5aee --- /dev/null +++ b/fr/03_Dessiner_un_triangle/00_Mise_en_place/01_Instance.md @@ -0,0 +1,174 @@ +## Création d'une instance + +La première chose à faire avec Vulkan est son initialisation au travers d'une *instance*. Cette instance relie +l'application à l'API. Pour la créer vous devrez donner quelques informations au driver. + +Créez une fonction `createInstance` et appelez-la depuis la fonction `initVulkan` : + +```c++ +void initVulkan() { + createInstance(); +} +``` + +Ajoutez ensuite un membre donnée représentant cette instance : + +```c++ +private: +VkInstance instance; +``` + +Pour créer l'instance, nous allons d'abord remplir une première structure avec des informations sur notre application. +Ces données sont optionnelles, mais elles peuvent fournir des informations utiles au driver pour optimiser ou +diagnostiquer les erreurs lors de l'exécution, par exemple en reconnaissant le nom d'un moteur graphique. Cette structure +s'appelle `VkApplicationInfo` : + +```c++ +void createInstance() { + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; +} +``` + +Comme mentionné précédemment, la plupart des structures Vulkan vous demandent d'expliciter leur propre type dans le +membre `sType`. Cela permet d'indiquer la version exacte de la structure que nous voulons utiliser : il y aura dans +le futur des extensions à celles-ci. Pour simplifier leur implémentation, les utiliser ne nécessitera que de changer +le type `VK_STRUCTURE_TYPE_XXX` en `VK_STRUCTURE_TYPE_XXX_2` (ou plus de 2) et de fournir une structure complémentaire +à l'aide du pointeur `pNext`. Nous n'utiliserons aucune extension, et donnerons donc toujours `nullptr` à `pNext`. + +Avec Vulkan, nous rencontrerons souvent (TRÈS souvent) des structures à remplir pour passer les informations à Vulkan. +Nous allons maintenant remplir le reste de la structure permettant la création de l'instance. Celle-ci n'est pas +optionnelle. Elle permet d'informer le driver des extensions et des validation layers que nous utiliserons, et ceci +de manière globale. Globale siginifie ici que ces données ne serons pas spécifiques à un périphérique. Nous verrons +la signification de cela dans les chapitres suivants. + +```c++ +VkInstanceCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; +createInfo.pApplicationInfo = &appInfo; +``` + +Les deux premiers paramètres sont simples. Les deux suivants spécifient les extensions dont nous aurons besoin. Comme +nous l'avons vu dans l'introduction, Vulkan ne connaît pas la plateforme sur laquelle il travaille, et nous aurons donc +besoin d'extensions pour utiliser des interfaces avec le gestionnaire de fenêtre. GLFW possède une fonction très +pratique qui nous donne la liste des extensions dont nous aurons besoin pour afficher nos résultats. Remplissez donc la +structure de ces données : + +```c++ +uint32_t glfwExtensionCount = 0; +const char** glfwExtensions; + +glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + +createInfo.enabledExtensionCount = glfwExtensionCount; +createInfo.ppEnabledExtensionNames = glfwExtensions; +``` + +Les deux derniers membres de la structure indiquent les validations layers à activer. Nous verrons cela dans le prochain +chapitre, laissez ces champs vides pour le moment : + +```c++ +createInfo.enabledLayerCount = 0; +``` + +Nous avons maintenant indiqué tout ce dont Vulkan a besoin pour créer notre première instance. Nous pouvons enfin +appeler `vkCreateInstance` : + +```c++ +VkResult result = vkCreateInstance(&createInfo, nullptr, &instance); +``` + +Comme vous le reverrez, l'appel à une fonction pour la création d'un objet Vulkan a le prototype suivant : + +* Pointeur sur une structure contenant l'information pour la création +* Pointeur sur une fonction d'allocation que nous laisserons toujours `nullptr` +* Pointeur sur une variable stockant une référence au nouvel objet + +Si tout s'est bien passé, la référence à l'instance devrait être contenue dans le membre `VkInstance`. Quasiment toutes +les fonctions Vulkan retournent une valeur de type VkResult, pouvant être soit `VK_SUCCESS` soit un code d'erreur. Afin +de vérifier si la création de l'instance s'est bien déroulée nous pouvons placer l'appel dans un `if` : + +```c++ +if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("Echec de la création de l'instance!"); +} +``` + +Lancez votre programme pour voir si l'instance s'est créée correctement. + +## Vérification du support des extensions + +Si vous regardez la documentation pour `vkCreateInstance` vous pourrez voir que l'un des messages d'erreur possible est +`VK_ERROR_EXTENSION_NOT_PRESENT`. Nous pourrions juste interrompre le programme et afficher une erreur si une extension +manque. Ce serait logique pour des fonctionnalités cruciales comme l'affichage, mais pas dans le cas d'extensions +optionnelles. + +La fonction `vkEnumerateInstanceExtensionProperties` permet de récupérer la totalité des extensions supportées par le +système avant la création de l'instance. Elle demande un pointeur vers une variable stockant le nombre d'extensions +supportées et un tableau où stocker des informations sur chacune des extensions. Elle possède également un paramètre +optionnel permettant de filtrer les résultats pour une validation layer spécifique. Nous l'ignorerons pour le moment. + +Pour allouer un tableau contenant les détails des extensions nous devons déjà connaître le nombre de ces extensions. +Vous pouvez ne demander que cette information en laissant le premier paramètre `nullptr` : + +```c++ +uint32_t extensionCount = 0; +vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); +``` + +Nous utiliserons souvent cette méthode. Allouez maintenant un tableau pour stocker les détails des extensions (incluez +) : + +```c++ +std::vector extensions(extensionCount); +``` + +Nous pouvons désormais accéder aux détails des extensions : + +```c++ +vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); +``` + +Chacune des structure `VkExtensionProperties` contient le nom et la version maximale supportée de l'extension. Nous +pouvons les afficher à l'aide d'une boucle `for` toute simple (`\t` représente une tabulation) : + +```c++ +std::cout << "Extensions disponibles :\n"; + +for (const auto& extension : extensions) { + std::cout << '\t' << extension.extensionName << '\n'; +} +``` + +Vous pouvez ajouter ce code dans la fonction `createInstance` si vous voulez indiquer des informations à propos du +support Vulkan sur la machine. Petit challenge : programmez une fonction vérifiant si les extensions dont vous avez +besoin (en particulier celles indiquées par GLFW) sont disponibles. + +## Libération des ressources + +L'instance contenue dans `VkInstance` ne doit être détruite qu'à la fin du programme. Nous la détruirons dans la +fonction `cleanup` grâce à la fonction `vkDestroyInstance` : + +```c++ +void cleanup() { + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +Les paramètres de cette fonction sont évidents. Nous y retrouvons le paramètre pour un désallocateur que nous laissons +`nullptr`. Toutes les ressources que nous allouerons à partir du prochain chapitre devront être libérées avant la +libération de l'instance. + +Avant d'avancer dans les notions plus complexes, créons un moyen de déboger notre programme avec +[les validations layers.](!fr/Dessiner_un_triangle/Mise_en_place/Validation_layers). + +[Code C++](/code/01_instance_creation.cpp) diff --git a/fr/03_Dessiner_un_triangle/00_Mise_en_place/02_Validation_layers.md b/fr/03_Dessiner_un_triangle/00_Mise_en_place/02_Validation_layers.md new file mode 100644 index 00000000..4a403547 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/00_Mise_en_place/02_Validation_layers.md @@ -0,0 +1,450 @@ +## Que sont les validation layers? + +L'API Vulkan est conçue pour limiter au maximum le travail du driver. Par conséquent il n'y a aucun traitement d'erreur +par défaut. Une erreur aussi simple que se tromper dans la valeur d'une énumération ou passer un pointeur nul comme +argument non optionnel résultent en un crash. Dans la mesure où Vulkan nous demande d'être complètement explicite, il +est facile d'utiliser une fonctionnalité optionnelle et d'oublier de mettre en place l'utilisation de l'extension à +laquelle elle appartient, par exemple. + +Cependant de telles vérifications peuvent être ajoutées à l'API. Vulkan possède un système élégant appelé validation +layers. Ce sont des composants optionnels s'insérant dans les appels des fonctions Vulkan pour y ajouter des opérations. +Voici un exemple d'opérations qu'elles réalisent : + +* Comparer les valeurs des paramètres à celles de la spécification pour détecter une mauvaise utilisation +* Suivre la création et la destruction des objets pour repérer les fuites de mémoire +* Vérifier la sécurité des threads en suivant l'origine des appels +* Afficher toutes les informations sur les appels à l'aide de la sortie standard +* Suivre les appels Vulkan pour créer une analyse dynamique de l'exécution du programme + +Voici ce à quoi une fonction de diagnostic pourrait ressembler : + +```c++ +VkResult vkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* instance) { + + if (pCreateInfo == nullptr || instance == nullptr) { + log("Pointeur nul passé à un paramètre obligatoire!"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + return real_vkCreateInstance(pCreateInfo, pAllocator, instance); +} +``` + +Les validation layers peuvent être combinées à loisir pour fournir toutes les fonctionnalités de débogage nécessaires. +Vous pouvez même activer les validations layers lors du développement et les désactiver lors du déploiement sans +aucun problème, sans aucune répercussion sur les performances et même sur l'exécutable! + +Vulkan ne fournit aucune validation layer, mais nous en avons dans le SDK de LunarG. Elles sont complètement [open +source](https://github.com/KhronosGroup/Vulkan-ValidationLayers), vous pouvez donc voir quelles erreurs elles suivent et +contribuer à leur développement. Les utiliser est la meilleure manière d'éviter que votre application fonctionne grâce +à un comportement spécifique à un driver. + +Les validations layers ne sont utilisables que si elles sont installées sur la machine. Il faut le SDK installé et +mis en place pour qu'elles fonctionnent. + +Il a existé deux formes de validation layers : les layers spécifiques à l'instance et celles spécifiques au physical +device (gpu). Elles ne vérifiaient ainsi respectivement que les appels aux fonctions d'ordre global et les appels aux +fonctions spécifiques au GPU. Les layers spécifiques du GPU sont désormais dépréciées. Les autres portent désormais sur +tous les appels. Cependant la spécification recommande encore que nous activions les validations layers au niveau du +logical device, car cela est requis par certaines implémentations. Nous nous contenterons de spécifier les mêmes +layers pour le logical device que pour le physical device, que nous verrons +[plus tard](!fr/Dessiner_un_triangle/Mise_en_place/Logical_device_et_queues). + +## Utiliser les validation layers + +Nous allons maintenant activer les validations layers fournies par le SDK de LunarG. Comme les extensions, nous devons +indiquer leurs nom. Au lieu de devoir spécifier les noms de chacune d'entre elles, nous pouvons les activer à l'aide +d'un nom générique : `VK_LAYER_KHRONOS_validation`. + +Mais ajoutons d'abord deux variables spécifiant les layers à activer et si le programme doit en effet les activer. J'ai +choisi d'effectuer ce choix selon si le programme est compilé en mode debug ou non. La macro `NDEBUG` fait partie +du standard c++ et correspond au second cas. + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG + constexpr bool enableValidationLayers = false; +#else + constexpr bool enableValidationLayers = true; +#endif +``` + +Ajoutons une nouvelle fonction `checkValidationLayerSupport`, qui devra vérifier si les layers que nous voulons utiliser +sont disponibles. Listez d'abord les validation layers disponibles à l'aide de la fonction +`vkEnumerateInstanceLayerProperties`. Elle s'utilise de la même façon que `vkEnumerateInstanceExtensionProperties`. + +```c++ +bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + return false; +} +``` + +Vérifiez que toutes les layers de `validationLayers` sont présentes dans la liste des layers disponibles. Vous aurez +besoin de `` pour la fonction `strcmp`. + +```c++ +for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } +} + +return true; +``` + +Nous pouvons maintenant utiliser cette fonction dans `createInstance` : + +```c++ +void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("les validations layers sont activées mais ne sont pas disponibles!"); + } + + ... +} +``` + +Lancez maintenant le programme en mode debug et assurez-vous qu'il fonctionne. Si vous obtenez une erreur, référez-vous +à la FAQ. + +Modifions enfin la structure `VkCreateInstanceInfo` pour inclure les noms des validation layers à utiliser lorsqu'elles +sont activées : + +```c++ +if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); +} else { + createInfo.enabledLayerCount = 0; +} +``` + +Si l'appel à la fonction `checkValidationLayerSupport` est un succès, `vkCreateInstance` ne devrait jamais retourner +`VK_ERROR_LAYER_NOT_PRESENT`, mais exécutez tout de même le programme pour être sûr que d'autres erreurs n'apparaissent +pas. + +## Fonction de rappel des erreurs + +Les validation layers affichent leur messages dans la console par défaut, mais on peut s'occuper de l'affichage nous-même en fournissant un callback explicite dans notre programme. Ceci nous permet également de choisir quels types de message afficher, car tous ne sont pas des erreurs (fatales). Si vous ne voulez pas vous occuper de ça maintenant, vous pouvez sauter à la dernière section de ce chapitre. + +Pour configurer un callback permettant de s'occuper des messages et des détails associés, nous devons mettre en place un debug messenger avec un callback en utilisant l'extension `VK_EXT_debug_utils`. + +Créons d'abord une fonction `getRequiredExtensions`. Elle nous fournira les extensions nécessaires selon que nous +activons les validation layers ou non : + +```c++ +std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; +} +``` + +Les extensions spécifiées par GLFW seront toujours nécessaires, mais celle pour le débogage n'est ajoutée que +conditionnellement. Remarquez l'utilisation de la macro `VK_EXT_DEBUG_UTILS_EXTENSION_NAME` au lieu du nom de +l'extension pour éviter les erreurs de frappe. + +Nous pouvons maintenant utiliser cette fonction dans `createInstance` : + +```c++ +auto extensions = getRequiredExtensions(); +createInfo.enabledExtensionCount = static_cast(extensions.size()); +createInfo.ppEnabledExtensionNames = extensions.data(); +``` + +Exécutez le programme et assurez-vous que vous ne recevez pas l'erreur `VK_ERROR_EXTENSION_NOT_PRESENT`. Nous ne devrions +pas avoir besoin de vérifier sa présence dans la mesure où les validation layers devraient impliquer son support, +mais sait-on jamais. + +Intéressons-nous maintenant à la fonction de rappel. Ajoutez la fonction statique `debugCallback` à votre classe avec le +prototype `PFN_vkDebugUtilsMessengerCallbackEXT`. `VKAPI_ATTR` et `VKAPI_CALL` assurent une compatibilité avec tous les +compilateurs. + +```c++ +static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; +} +``` + +Le premier paramètre indique la sévérité du message, et peut prendre les valeurs suivantes : + +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT`: Message de suivi des appels +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`: Message d'information (allocation d'une ressource...) +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT`: Message relevant un comportement qui n'est pas un bug mais plutôt +une imperfection involontaire +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT`: Message relevant un comportement invalide pouvant mener à un crash + +Les valeurs de cette énumération on été conçues de telle sorte qu'il est possible de les comparer pour vérifier la +sévérité d'un message, par exemple : + +```c++ +if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + // Le message est suffisamment important pour être affiché +} +``` + +Le paramètre `messageType` peut prendre les valeurs suivantes : + +* `VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT` : Un événement quelconque est survenu, sans lien avec les +performances ou la spécification +* `VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT` : Une violation de la spécification ou une potentielle erreur est +survenue +* `VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT` : Utilisation potentiellement non optimale de Vulkan + +Le paramètre `pCallbackData` est une structure du type `VkDebugUtilsMessengerCallbackDataEXT` contenant les détails du +message. Ses membres les plus importants sont : + +* `pMessage`: Le message sous la forme d'une chaîne de type C terminée par le caractère nul `\0` +* `pObjects`: Un tableau d'objets Vulkan liés au message +* `objectCount`: Le nombre d'objets dans le tableau précédent + +Finalement, le paramètre `pUserData` est un pointeur sur une donnée quelconque que vous pouvez spécifier à la création +de la fonction de rappel. + +La fonction de rappel que nous programmons retourne un booléen déterminant si la fonction à l'origine de son appel doit +être interrompue. Si elle retourne `VK_TRUE`, l'exécution de la fonction est interrompue et cette dernière retourne +`VK_ERROR_VALIDATION_FAILED_EXT`. Cette fonctionnalité n'est globalement utilisée que pour tester les validation layers +elles-mêmes, nous retournerons donc invariablement `VK_FALSE`. + +Il ne nous reste plus qu'à fournir notre fonction à Vulkan. Surprenamment, même le messager de débogage se +gère à travers une référence de type `VkDebugUtilsMessengerEXT`, que nous devrons explicitement créer et détruire. Une +telle fonction de rappel est appelée *messager*, et vous pouvez en posséder autant que vous le désirez. Ajoutez un +membre donnée pour le messager sous l'instance : + +```c++ +VkDebugUtilsMessengerEXT callback; +``` + +Ajoutez ensuite une fonction `setupDebugMessenger` et appelez la dans `initVulkan` après `createInstance` : + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); +} + +void setupDebugMessenger() { + if (!enableValidationLayers) return; + +} +``` + +Nous devons maintenant remplir une structure avec des informations sur le messager : + +```c++ +VkDebugUtilsMessengerCreateInfoEXT createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; +createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; +createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; +createInfo.pfnUserCallback = debugCallback; +createInfo.pUserData = nullptr; // Optionnel +``` + +Le champ `messageSeverity` vous permet de filtrer les niveaux de sévérité pour lesquels la fonction de rappel sera +appelée. J'ai laissé tous les types sauf `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`, ce qui permet de recevoir +toutes les informations à propos de possibles bugs tout en éliminant la verbose. + +De manière similaire, le champ `messageType` vous permet de filtrer les types de message pour lesquels la fonction de +rappel sera appelée. J'y ai mis tous les types possibles. Vous pouvez très bien en désactiver s'ils ne vous servent à +rien. + +Le champ `pfnUserCallback` indique le pointeur vers la fonction de rappel. + +Vous pouvez optionnellement ajouter un pointeur sur une donnée de votre choix grâce au champ `pUserData`. Le pointeur +fait partie des paramètres de la fonction de rappel. + +Notez qu'il existe de nombreuses autres manières de configurer des messagers auprès des validation layers, mais nous +avons ici une bonne base pour ce tutoriel. Référez-vous à la +[spécification de l'extension](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap50.html#VK_EXT_debug_utils) +pour plus d'informations sur ces possibilités. + +Cette structure doit maintenant être passée à la fonction `vkCreateDebugUtilsMessengerEXT` afin de créer l'objet +`VkDebugUtilsMessengerEXT`. Malheureusement cette fonction fait partie d'une extension non incluse par GLFW. Nous devons +donc gérer son activation nous-mêmes. Nous utiliserons la fonction `vkGetInstancePorcAddr` pous en +récupérer un pointeur. Nous allons créer notre propre fonction - servant de proxy - pour abstraire cela. Je l'ai ajoutée +au-dessus de la définition de la classe `HelloTriangleApplication`. + +```c++ +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pCallback) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pCallback); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} +``` + +La fonction `vkGetInstanceProcAddr` retourne `nullptr` si la fonction n'a pas pu être chargée. Nous pouvons maintenant +utiliser cette fonction pour créer le messager s'il est disponible : + +```c++ +if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { + throw std::runtime_error("le messager n'a pas pu être créé!"); +} +``` + +Le troisième paramètre est l'invariable allocateur optionnel que nous laissons `nullptr`. Les autres paramètres sont +assez logiques. La fonction de rappel est spécifique de l'instance et des validation layers, nous devons donc passer +l'instance en premier argument. Lancez le programme et vérifiez qu'il fonctionne. Vous devriez avoir le résultat +suivant : + +![](/images/validation_layer_test.png) + +qui indique déjà un bug dans notre application! En effet l'objet `VkDebugUtilsMessengerEXT` doit être libéré +explicitement à l'aide de la fonction `vkDestroyDebugUtilsMessagerEXT`. De même qu'avec +`vkCreateDebugUtilsMessangerEXT` nous devons charger dynamiquement cette fonction. Notez qu'il est normal que le +message s'affiche plusieurs fois; il y a plusieurs validation layers, et dans certains cas leurs domaines d'expertise +se recoupent. + +Créez une autre fonction proxy en-dessous de `CreateDebugUtilsMessengerEXT` : + +```c++ +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT callback, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, callback, pAllocator); + } +} +``` + +Nous pouvons maintenant l'appeler dans notre fonction `cleanup` : + +```c++ +void cleanup() { + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, callback, nullptr); + } + + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +Si vous exécutez le programme maintenant, vous devriez constater que le message n'apparait plus. Si vous voulez voir +quel fonction a lancé un appel au messager, vous pouvez insérer un point d'arrêt dans la fonction de rappel. + +## Déboguer la création et la destruction de l'instance + +Même si nous avons mis en place un système de débogage très efficace, deux fonctions passent sous le radar. Comme il +est nécessaire d'avoir une instance pour appeler `vkCreateDebugUtilsMessengerEXT`, la création de l'instance n'est pas +couverte par le messager. Le même problème apparait avec la destruction de l'instance. + +En lisant +[la documentation](https://github.com/KhronosGroup/Vulkan-Docs/blob/main/appendices/VK_EXT_debug_utils.adoc#examples) on +voit qu'il existe un messager spécifiquement créé pour ces deux fonctions. Il suffit de passer un pointeur vers une +instance de `VkDebugUtilsMessengerCreateInfoEXT` au membre `pNext` de `VkInstanceCreateInfo`. Plaçons le remplissage de +la structure de création du messager dans une fonction : + +```c++ +void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; +} +... +void setupDebugMessenger() { + if (!enableValidationLayers) return; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } +} +``` + +Nous pouvons réutiliser cette fonction dans `createInstance` : + +```c++ +void createInstance() { + ... + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + ... + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } +} +``` + +La variable `debugCreateInfo` est en-dehors du `if` pour qu'elle ne soit pas détruite avant l'appel à +`vkCreateInstance`. La structure fournie à la création de l'instance à travers la structure `VkInstanceCreateInfo` +mènera à la création d'un messager spécifique aux deux fonctions qui sera détruit automatiquement à la destruction de +l'instance. + +## Configuration + +Les validation layers peuvent être paramétrées de nombreuses autres manières que juste avec les informations que nous +avons fournies dans la structure `VkDebugUtilsMessangerCreateInfoEXT`. Ouvrez le SDK Vulkan et rendez-vous dans le +dossier `Config`. Vous y trouverez le fichier `vk_layer_settings.txt` qui vous expliquera comment configurer les +validation layers. + +Pour configurer les layers pour votre propre application, copiez le fichier dans les dossiers `Debug` et/ou `Release`, +puis suivez les instructions pour obtenir le comportement que vous souhaitez. Cependant, pour le reste du tutoriel, je +partirai du principe que vous les avez laissées avec leur comportement par défaut. + +Tout au long du tutoriel je laisserai de petites erreurs intentionnelles pour vous montrer à quel point les validation +layers sont pratiques, et à quel point vous devez comprendre tout ce que vous faites avec Vulkan. Il est maintenant +temps de s'intéresser aux [devices Vulkan dans le système](!fr/Dessiner_un_triangle/Mise_en_place/Physical_devices_et_queue_families). + +[Code C++](/code/02_validation_layers.cpp) diff --git a/fr/03_Dessiner_un_triangle/00_Mise_en_place/03_Physical_devices_et_queue_families.md b/fr/03_Dessiner_un_triangle/00_Mise_en_place/03_Physical_devices_et_queue_families.md new file mode 100644 index 00000000..2ca65359 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/00_Mise_en_place/03_Physical_devices_et_queue_families.md @@ -0,0 +1,346 @@ +## Sélection d'un physical device + +La librairie étant initialisée à travers `VkInstance`, nous pouvons dès à présent chercher et sélectionner une carte +graphique (physical device) dans le système qui supporte les fonctionnalitées dont nous aurons besoin. Nous pouvons en +fait en sélectionner autant que nous voulons et travailler avec chacune d'entre elles, mais nous n'en utiliserons qu'une +dans ce tutoriel pour des raisons de simplicité. + +Ajoutez la fonction `pickPhysicalDevice` et appelez la depuis `initVulkan` : + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + pickPhysicalDevice(); +} + +void pickPhysicalDevice() { + +} +``` + +Nous stockerons le physical device que nous aurons sélectionnée dans un nouveau membre donnée de la classe, et celui-ci +sera du type `VkPhysicalDevice`. Cette référence sera implicitement détruit avec l'instance, nous n'avons donc rien à +ajouter à la fonction `cleanup`. + +```c++ +VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; +``` + +Lister les physical devices est un procédé très similaire à lister les extensions. Comme d'habitude, on commence par en +lister le nombre. + +```c++ +uint32_t deviceCount = 0; +vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); +``` + +Si aucun physical device ne supporte Vulkan, il est inutile de continuer l'exécution. + +```c++ +if (deviceCount == 0) { + throw std::runtime_error("aucune carte graphique ne supporte Vulkan!"); +} +``` + +Nous pouvons ensuite allouer un tableau contenant toutes les références aux `VkPhysicalDevice`. + +```c++ +std::vector devices(deviceCount); +vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); +``` + +Nous devons maintenant évaluer chacun des gpus et vérifier qu'ils conviennent pour ce que nous voudrons en faire, car +toutes les cartes graphiques n'ont pas été crées égales. Voici une nouvelle fonction qui fera le travail de +sélection : + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + return true; +} +``` + +Nous allons dans cette fonction vérifier que le physical device respecte nos conditions. + +```c++ +for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } +} + +if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("aucun GPU ne peut exécuter ce programme!"); +} +``` + +La section suivante introduira les premières contraintes que devront remplir les physical devices. Au fur et à mesure +que nous utiliserons de nouvelles fonctionnalités, nous les ajouterons dans cette fonction. + +## Vérification des fonctionnalités de base + +Pour évaluer la compatibilité d'un physical device nous devons d'abord nous informer sur ses capacités. Des propriétés +basiques comme le nom, le type et les versions de Vulkan supportées peuvent être obtenues en appelant +`vkGetPhysicalDeviceProperties`. + +```c++ +VkPhysicalDeviceProperties deviceProperties; +vkGetPhysicalDeviceProperties(device, &deviceProperties); +``` + +Le support des fonctionnalités optionnelles telles que les textures compressées, les floats de 64 bits et le multi +viewport rendering (pour la VR) s'obtiennent avec `vkGetPhysicalDeviceFeatures` : + +```c++ +VkPhysicalDeviceFeatures deviceFeatures; +vkGetPhysicalDeviceFeatures(device, &deviceFeatures); +``` + +De nombreux autres détails intéressants peuvent être requis, mais nous en remparlerons dans les prochains chapitres. + +Voyons un premier exemple. Considérons que notre application a besoin d'une carte graphique dédiée supportant les +geometry shaders. Notre fonction `isDeviceSuitable` ressemblerait alors à cela : + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + VkPhysicalDeviceProperties deviceProperties; + VkPhysicalDeviceFeatures deviceFeatures; + vkGetPhysicalDeviceProperties(device, &deviceProperties); + vkGetPhysicalDeviceFeatures(device, &deviceFeatures); + + return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && + deviceFeatures.geometryShader; +} +``` + +Au lieu de choisir le premier physical device nous convenant, nous pourrions attribuer un score à chacun d'entre eux et +utiliser celui dont le score est le plus élevé. Vous pourriez ainsi préférer une carte graphique dédiée, mais utiliser +un GPU intégré au CPU si le système n'en détecte aucune. Vous pourriez implémenter ce concept comme cela : + +```c++ +#include + +... + +void pickPhysicalDevice() { + ... + + // L'utilisation d'une map permet de les trier automatiquement de manière ascendante + std::multimap candidates; + + for (const auto& device : devices) { + int score = rateDeviceSuitability(device); + candidates.insert(std::make_pair(score, device)); + } + + // Voyons si la meilleure possède les fonctionnalités dont nous ne pouvons nous passer + if (candidates.rbegin()->first > 0) { + physicalDevice = candidates.rbegin()->second; + } else { + throw std::runtime_error("aucun GPU ne peut executer ce programme!"); + } +} + +int rateDeviceSuitability(VkPhysicalDevice device) { + ... + + int score = 0; + + // Les carte graphiques dédiées ont un énorme avantage en terme de performances + if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + score += 1000; + } + + // La taille maximale des textures affecte leur qualité + score += deviceProperties.limits.maxImageDimension2D; + + // L'application (fictive) ne peut fonctionner sans les geometry shaders + if (!deviceFeatures.geometryShader) { + return 0; + } + + return score; +} +``` + +Vous n'avez pas besoin d'implémenter tout ça pour ce tutoriel, mais faites-le si vous voulez, à titre d'entrainement. +Vous pourriez également vous contenter d'afficher les noms des cartes graphiques et laisser l'utilisateur choisir. + +Nous ne faisons que commencer donc nous prendrons la première carte supportant Vulkan : + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + return true; +} +``` + +Nous discuterons de la première fonctionnalité qui nous sera nécessaire dans la section suivante. + +## Familles de queues (queue families) + +Il a été évoqué que chaque opération avec Vulkan, de l'affichage jusqu'au chargement d'une texture, s'effectue en +ajoutant une commande à une queue. Il existe différentes queues appartenant à différents types de +*queue families*. De plus chaque queue family ne permet que certaines commandes. Il se peut par exemple qu'une queue ne +traite que les commandes de calcul et qu'une autre ne supporte que les commandes d'allocation de mémoire. + +Nous devons analyser quelles queue families existent sur le système et lesquelles correspondent aux commandes que nous +souhaitons utiliser. Nous allons donc créer la fonction `findQueueFamilies` dans laquelle nous chercherons les +commandes nous intéressant. + +Nous allons chercher une queue qui supporte les commandes graphiques, la fonction pourrait ressembler à ça: + +```c++ +uint32_t findQueueFamilies(VkPhysicalDevice device) { + // Code servant à trouver la famille de queue "graphique" +} +``` + +Mais dans un des prochains chapitres, nous allons avoir besoin d'une autre famille de queues, il est donc plus intéressant +de s'y préparer dès maintenant en empactant plusieurs indices dans une structure: + +```c++ +struct QueueFamilyIndices { + uint32_t graphicsFamily; +}; + +QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + // Code pour trouver les indices de familles à ajouter à la structure + return indices +} +``` + +Que se passe-t-il si une famille n'est pas disponible ? On pourrait lancer une exception dans `findQueueFamilies`, +mais cette fonction n'est pas vraiment le bon endroit pour prendre des decisions concernant le choix du bon Device. +Par exemple, on pourrait *préférer* des Devices avec une queue de transfert dédiée, sans toutefois le requérir. +Par conséquent nous avons besoin d'indiquer si une certaine famille de queues à été trouvé. + +Ce n'est pas très pratique d'utiliser une valeur magique pour indiquer la non-existence d'une famille, comme n'importe +quelle valeur de `uint32_t` peut théoriquement être une valeur valide d'index de famille, incluant `0`. +Heureusement, le C++17 introduit un type qui permet la distinction entre le cas où la valeur existe et celui +où elle n'existe pas: + +```c++ +#include + +... + +std::optional graphicsFamily; + +std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // faux + +graphicsFamily = 0; + +std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // vrai +``` + +`std::optional` est un wrapper qui ne contient aucune valeur tant que vous ne lui en assignez pas une. +Vous pouvez, quelque soit le moment, lui demander si il contient une valeur ou non en appelant sa fonction membre +`has_value()`. On peut donc changer le code comme suit: + +```c++ +#include + +... + +struct QueueFamilyIndices { + std::optional graphicsFamily; +}; + +QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + // Assigne l'index aux familles qui ont pu être trouvées + + return indices; +} +``` + +On peut maintenant commencer à implémenter `findQueueFamilies`: + +```c++ +QueueFamilyIndices findQueueFamily(VkPhysicalDevice) { + QueueFamilyIndices indices; + + ... + + return indices; +} +``` + +Récupérer la liste des queue families disponibles se fait de la même manière que d'habitude, avec la fonction +`vkGetPhysicalDeviceQueueFamilyProperties` : + +```c++ +uint32_t queueFamilyCount = 0; +vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + +std::vector queueFamilies(queueFamilyCount); +vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); +``` + +La structure `VkQueueFamilyProperties` contient des informations sur la queue family, et en particulier le type +d'opérations qu'elle supporte et le nombre de queues que l'on peut instancier à partir de cette famille. Nous devons +trouver au moins une queue supportant `VK_QUEUE_GRAPHICS_BIT` : + +```c++ +int i = 0; +for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + i++; +} +``` + +Nous pouvons maintenant utiliser cette fonction dans `isDeviceSuitable` pour s'assurer que le physical device peut +recevoir les commandes que nous voulons lui envoyer : + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.graphicsFamily.has_value(); +} +``` + +Pour que ce soit plus pratique, nous allons aussi ajouter une fonction générique à la structure: + +```c++ +struct QueueFamilyIndices { + std::optional graphicsFamily; + + bool isComplete() { + return graphicsFamily.has_value(); + } +}; + +... + +bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.isComplete(); +} +``` + +On peut également utiliser ceci pour sortir plus tôt de `findQueueFamilies`: + +```c++ +for (const auto& queueFamily : queueFamilies) { + ... + + if (indices.isComplete()) { + break; + } + + i++; +} +``` + +Bien, c'est tout ce dont nous aurons besoin pour choisir le bon physical device! La prochaine étape est de [créer un +logical device](!fr/Dessiner_un_triangle/Mise_en_place/Logical_device_et_queues) pour créer une interface avec la carte. + +[Code C++](/code/03_physical_device_selection.cpp) diff --git a/fr/03_Dessiner_un_triangle/00_Mise_en_place/04_Logical_device_et_queues.md b/fr/03_Dessiner_un_triangle/00_Mise_en_place/04_Logical_device_et_queues.md new file mode 100644 index 00000000..c6cbdcc6 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/00_Mise_en_place/04_Logical_device_et_queues.md @@ -0,0 +1,159 @@ +## Introduction + +La sélection d'un physical device faite, nous devons générer un *logical device* pour servir d'interface. Le +processus de sa création est similaire à celui de l'instance : nous devons décrire ce dont nous aurons besoin. Nous +devons également spécifier les queues dont nous aurons besoin. Vous pouvez également créer plusieurs logical devices à +partir d'un physical device si vous en avez besoin. + +Commencez par ajouter un nouveau membre donnée pour stocker la référence au logical device. + +```c++ +VkDevice device; +``` + +Ajoutez ensuite une fonction `createLogicalDevice` et appelez-la depuis `initVulkan`. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + pickPhysicalDevice(); + createLogicalDevice(); +} + +void createLogicalDevice() { + +} +``` + +## Spécifier les queues à créer + +La création d'un logical device requiert encore que nous remplissions des informations dans des structures. La +première de ces structures s'appelle `VkDeviceQueueCreateInfo`. Elle indique le nombre de queues que nous désirons pour +chaque queue family. Pour le moment nous n'avons besoin que d'une queue originaire d'une unique queue family : la +première avec un support pour les graphismes. + +```c++ +QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + +VkDeviceQueueCreateInfo queueCreateInfo{}; +queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; +queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value(); +queueCreateInfo.queueCount = 1; +``` + +Actuellement les drivers ne vous permettent que de créer un petit nombre de queues pour chacune des familles, et vous +n'avez en effet pas besoin de plus. Vous pouvez très bien créer les commandes (command buffers) depuis plusieurs +threads et les soumettre à la queue d'un coup sur le thread principal, et ce sans perte de performance. + +Vulkan permet d'assigner des niveaux de priorité aux queues à l'aide de floats compris entre `0.0` et `1.0`. Vous +pouvez ainsi influencer l'exécution des command buffers. Il est nécessaire d'indiquer une priorité même lorsqu'une +seule queue est présente : + +```c++ +float queuePriority = 1.0f; +queueCreateInfo.pQueuePriorities = &queuePriority; +``` + +## Spécifier les fonctionnalités utilisées + +Les prochaines informations à fournir sont les fonctionnalités du physical device que nous souhaitons utiliser. Ce +sont celles dont nous avons vérifié la présence avec `vkGetPhysicalDeviceFeatures` dans le chapitre précédent. Nous +n'avons besoin de rien de spécial pour l'instant, nous pouvons donc nous contenter de créer la structure et de tout +laisser à `VK_FALSE`, valeur par défaut. Nous reviendrons sur cette structure quand nous ferons des choses plus +intéressantes avec Vulkan. + +```c++ +VkPhysicalDeviceFeatures deviceFeatures{}; +``` + +## Créer le logical device + +Avec ces deux structure prêtes, nous pouvons enfin remplir la structure principale appelée `VkDeviceCreateInfo`. + +```c++ +VkDeviceCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; +``` + +Référencez d'abord les structures sur la création des queues et sur les fonctionnalités utilisées : + +```c++ +createInfo.pQueueCreateInfos = &queueCreateInfo; +createInfo.queueCreateInfoCount = 1; + +createInfo.pEnabledFeatures = &deviceFeatures; +``` + +Le reste ressemble à la structure `VkInstanceCreateInfo`. Nous devons spécifier les extensions spécifiques de la +carte graphique et les validation layers. + +Un exemple d'extension spécifique au GPU est `VK_KHR_swapchain`. Celle-ci vous permet de présenter à l'écran les images +sur lesquels votre programme a effectué un rendu. Il est en effet possible que certains GPU ne possèdent pas cette +capacité, par exemple parce qu'ils ne supportent que les compute shaders. Nous reviendrons sur cette extension +dans le chapitre dédié à la swap chain. + +Comme dit dans le chapitre sur les validation layers, nous activerons les mêmes que celles que nous avons spécifiées +lors de la création de l'instance. Nous n'avons pour l'instant besoin d'aucune validation layer en particulier. Notez +que le standard ne fait plus la différence entre les extensions de l'instance et celles du device, au point que les +paramètres `enabledLayerCount` et `ppEnabledLayerNames` seront probablement ignorés. Nous les remplissons quand même +pour s'assurer de la bonne compatibilité avec les anciennes implémentations. + +```c++ +createInfo.enabledExtensionCount = 0; + +if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); +} else { + createInfo.enabledLayerCount = 0; +} +``` + +C'est bon, nous pouvons maintenant instancier le logical device en appelant la fonction `vkCreateDevice`. + +```c++ +if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("échec lors de la création d'un logical device!"); +} +``` + +Les paramètres sont d'abord le physical device dont on souhaite extraire une interface, ensuite la structure contenant +les informations, puis un pointeur optionnel pour l'allocation et enfin un pointeur sur la référence au logical +device créé. Vérifions également si la création a été un succès ou non, comme lors de la création de l'instance. + +Le logical device doit être explicitement détruit dans la fonction `cleanup` avant le physical device : + +```c++ +void cleanup() { + vkDestroyDevice(device, nullptr); + ... +} +``` + +Les logical devices n'interagissent pas directement avec l'instance mais seulement avec le physical device, c'est +pourquoi il n'y a pas de paramètre pour l'instance. + +## Récupérer des références aux queues + +Les queue families sont automatiquement crées avec le logical device. Cependant nous n'avons aucune interface avec +elles. Ajoutez un membre donnée pour stocker une référence à la queue family supportant les graphismes : + +```c++ +VkQueue graphicsQueue; +``` + +Les queues sont implicitement détruites avec le logical device, nous n'avons donc pas à nous en charger dans `cleanup`. + +Nous pourrons ensuite récupérer des références à des queues avec la fonction `vkGetDeviceQueue`. Les paramètres en +sont le logical device, la queue family, l'indice de la queue à récupérer et un pointeur où stocker la référence à la +queue. Nous ne créons qu'une seule queue, nous écrirons donc `0` pour l'indice de la queue. + +```c++ +vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); +``` + +Avec le logical device et les queues nous allons maintenant pouvoir faire travailler la carte graphique! Dans le +prochain chapitre nous mettrons en place les ressources nécessaires à la présentation des images à l'écran. + +[Code C++](/code/04_logical_device.cpp) diff --git "a/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/00_Window_surface.md" "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/00_Window_surface.md" new file mode 100644 index 00000000..93908382 --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/00_Window_surface.md" @@ -0,0 +1,206 @@ +## Introduction + +Vulkan ignore la plateforme sur laquelle il opère et ne peut donc pas directement établir d'interface avec le +gestionnaire de fenêtres. Pour créer une interface permettant de présenter les rendus à l'écran, nous devons utiliser +l'extension WSI (Window System Integration). Nous verrons dans ce chapitre l'extension `VK_KHR_surface`, l'une des +extensions du WSI. Nous pourrons ainsi obtenir l'objet `VkSurfaceKHR`, qui est un type abstrait de surface sur +lequel nous pourrons effectuer des rendus. Cette surface sera en lien avec la fenêtre que nous avons créée grâce à GLFW. + +L'extension `VK_KHR_surface`, qui se charge au niveau de l'instance, a déjà été ajoutée, car elle fait partie des +extensions retournées par la fonction `glfwGetRequiredInstanceExtensions`. Les autres fonctions WSI que nous verrons +dans les prochains chapitres feront aussi partie des extensions retournées par cette fonction. + +La surface de fenêtre doit être créée juste après l'instance car elle peut influencer le choix du physical device. +Nous ne nous intéressons à ce sujet que maintenant car il fait partie du grand ensemble que nous abordons et qu'en +parler plus tôt aurait été confus. Il est important de noter que cette surface est complètement optionnelle, et vous +pouvez l'ignorer si vous voulez effectuer du rendu off-screen ou du calculus. Vulkan vous offre ces possibilités sans +vous demander de recourir à des astuces comme créer une fenêtre invisible, là où d'autres APIs le demandaient (cf +OpenGL). + +## Création de la window surface + +Commencez par ajouter un membre donnée `surface` sous le messager. + +```c++ +VkSurfaceKHR surface; +``` + +Bien que l'utilisation d'un objet `VkSurfaceKHR` soit indépendant de la plateforme, sa création ne l'est pas. +Celle-ci requiert par exemple des références à `HWND` et à `HMODULE` sous Windows. C'est pourquoi il existe des +extensions spécifiques à la plateforme, dont par exemple `VK_KHR_win32_surface` sous Windows, mais celles-ci sont +aussi évaluées par GLFW et intégrées dans les extensions retournées par la fonction `glfwGetRequiredInstanceExtensions`. + +Nous allons voir l'exemple de la création de la surface sous Windows, même si nous n'utiliserons pas cette méthode. +Il est en effet contre-productif d'utiliser une librairie comme GLFW et un API comme Vulkan pour se retrouver à écrire +du code spécifique à la plateforme. La fonction de GLFW `glfwCreateWindowSurface` permet de gérer les différences de +plateforme. Cet exemple ne servira ainsi qu'à présenter le travail de bas niveau, dont la connaissance est toujours +utile à une bonne utilisation de Vulkan. + +Une window surface est un objet Vulkan comme un autre et nécessite donc de remplir une structure, ici +`VkWin32SurfaceCreateInfoKHR`. Elle possède deux paramètres importants : `hwnd` et `hinstance`. Ce sont les références +à la fenêtre et au processus courant. + +```c++ +VkWin32SurfaceCreateInfoKHR createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; +createInfo.hwnd = glfwGetWin32Window(window); +createInfo.hinstance = GetModuleHandle(nullptr); +``` + +Nous pouvons extraire `HWND` de la fenêtre à l'aide de la fonction `glfwGetWin32Window`. La fonction +`GetModuleHandle` fournit une référence au `HINSTANCE` du thread courant. + +La surface peut maintenant être crée avec `vkCreateWin32SurfaceKHR`. Cette fonction prend en paramètre une instance, des +détails sur la création de la surface, l'allocateur optionnel et la variable dans laquelle placer la référence. Bien que +cette fonction fasse partie d'une extension, elle est si communément utilisée qu'elle est chargée par défaut par Vulkan. +Nous n'avons ainsi pas à la charger à la main : + +```c++ +if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("échec de la creation d'une window surface!"); +} +``` + +Ce processus est similaire pour Linux, où la fonction `vkCreateXcbSurfaceKHR` requiert la fenêtre et une connexion à +XCB comme paramètres pour X11. + +La fonction `glfwCreateWindowSurface` implémente donc tout cela pour nous et utilise le code correspondant à la bonne +plateforme. Nous devons maintenant l'intégrer à notre programme. Ajoutez la fonction `createSurface` et appelez-la +dans `initVulkan` après la création de l'instance et du messager : + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); +} + +void createSurface() { + +} +``` + +L'appel à la fonction fournie par GLFW ne prend que quelques paramètres au lieu d'une structure, ce qui rend le tout +très simple : + +```c++ +void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("échec de la création de la window surface!"); + } +} +``` + +Les paramètres sont l'instance, le pointeur sur la fenêtre, l'allocateur optionnel et un pointeur sur une variable de +type `VkSurfaceKHR`. GLFW ne fournit aucune fonction pour détruire cette surface mais nous pouvons le faire +nous-mêmes avec une simple fonction Vulkan : + +```c++ +void cleanup() { + ... + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + ... + } +``` + +Détruisez bien la surface avant l'instance. + +## Demander le support pour la présentation + +Bien que l'implémentation de Vulkan supporte le WSI, il est possible que d'autres éléments du système ne le supportent +pas. Nous devons donc allonger `isDeviceSuitable` pour s'assurer que le logical device puisse présenter les +rendus à la surface que nous avons créée. La présentation est spécifique aux queues families, ce qui signifie que +nous devons en fait trouver une queue family supportant cette présentation. + +Il est possible que les queue families supportant les commandes d'affichage et celles supportant les commandes de +présentation ne soient pas les mêmes, nous devons donc considérer que ces deux queues sont différentes. En fait, les +spécificités des queues families diffèrent majoritairement entre les vendeurs, et assez peu entre les modèles d'une même +série. Nous devons donc étendre la structure `QueueFamilyIndices` : + +```c++ +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; +``` + +Nous devons ensuite modifier la fonction `findQueueFamilies` pour qu'elle cherche une queue family pouvant supporter +les commandes de présentation. La fonction qui nous sera utile pour cela est `vkGetPhysicalDeviceSurfaceSupportKHR`. +Elle possède quatre paramètres, le physical device, un indice de queue family, la surface et un booléen. Appelez-la +depuis la même boucle que pour `VK_QUEUE_GRAPHICS_BIT` : + +```c++ +VkBool32 presentSupport = false; +vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); +``` + +Vérifiez simplement la valeur du booléen et stockez la queue dans la structure si elle est intéressante : + +```c++ +if (presentSupport) { + indices.presentFamily = i; +} +``` + +Il est très probable que ces deux queue families soient en fait les mêmes, mais nous les traiterons comme si elles +étaient différentes pour une meilleure compatibilité. Vous pouvez cependant ajouter un alorithme préférant des +queues combinées pour améliorer légèrement les performances. + +## Création de la queue de présentation (presentation queue) + +Il nous reste à modifier la création du logical device pour extraire de celui-ci la référence à une presentation queue +`VkQueue`. Ajoutez un membre donnée pour cette référence : + +```c++ +VkQueue presentQueue; +``` + +Nous avons besoin de plusieurs structures `VkDeviceQueueCreateInfo`, une pour chaque queue family. Une manière de +gérer ces structures est d'utiliser un `set` contenant tous les indices des queues et un `vector` pour les structures : + +```c++ +#include + +... + +QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + +std::vector queueCreateInfos; +std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + +float queuePriority = 1.0f; +for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); +} +``` + +Puis modifiez `VkDeviceCreateInfo` pour qu'il pointe sur le contenu du vector : + +```c++ +createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); +createInfo.pQueueCreateInfos = queueCreateInfos.data(); +``` + +Si les queues sont les mêmes, nous n'avons besoin de les indiquer qu'une seule fois, ce dont le set s'assure. Ajoutez +enfin un appel pour récupérer les queue families : + +```c++ +vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); +``` + +Si les queues sont les mêmes, les variables contenant les références contiennent la même valeur. Dans le prochain +chapitre nous nous intéresserons aux swap chain, et verrons comment elle permet de présenter les rendus à l'écran. + +[Code C++](/code/05_window_surface.cpp) diff --git "a/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/01_Swap_chain.md" "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/01_Swap_chain.md" new file mode 100644 index 00000000..539efe5b --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/01_Swap_chain.md" @@ -0,0 +1,543 @@ +Vulkan ne possède pas de concept comme le framebuffer par défaut, et nous devons donc créer une infrastructure qui +contiendra les buffers sur lesquels nous effectuerons les rendus avant de les présenter à l'écran. Cette +infrastructure s'appelle _swap chain_ sur Vulkan et doit être créée explicitement. La swap chain est essentiellement +une file d'attente d'images attendant d'être affichées. Notre application devra récupérer une des images de la file, +dessiner dessus puis la retourner à la file d'attente. Le fonctionnement de la file d'attente et les conditions de la +présentation dépendent du paramétrage de la swap chain. Cependant, l'intérêt principal de la swap chain est de +synchroniser la présentation avec le rafraîchissement de l'écran. + +## Vérification du support de la swap chain + +Toutes les cartes graphiques ne sont pas capables de présenter directement les images à l'écran, et ce pour +différentes raisons. Ce pourrait être car elles sont destinées à être utilisées dans un serveur et n'ont aucune +sortie vidéo. De plus, dans la mesure où la présentation est très dépendante du gestionnaire de fenêtres et de la +surface, la swap chain ne fait pas partie de Vulkan "core". Il faudra donc utiliser des extensions, dont +`VK_KHR_swapchain`. + +Pour cela nous allons modifier `isDeviceSuitable` pour qu'elle vérifie si cette extension est supportée. Nous avons +déjà vu comment lister les extensions supportées par un `VkPhysicalDevice` donc cette modification devrait être assez +simple. Notez que le header Vulkan intègre la macro `VK_KHR_SWAPCHAIN_EXTENSION_NAME` qui permet d'éviter une faute +de frappe. Toutes les extensions ont leur macro. + +Déclarez d'abord une liste d'extensions nécessaires au physical device, comme nous l'avons fait pour les validation +layers : + +```c++ +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; +``` + +Créez ensuite une nouvelle fonction appelée `checkDeviceExtensionSupport` et appelez-la depuis `isDeviceSuitable` +comme vérification supplémentaire : + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + return indices.isComplete() && extensionsSupported; +} + +bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + return true; +} +``` + +Énumérez les extensions et vérifiez si toutes les extensions requises en font partie. + +```c++ +bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); +} +``` + +J'ai décidé d'utiliser une collection de strings pour représenter les extensions requises en attente de confirmation. +Nous pouvons ainsi facilement les éliminer en énumérant la séquence. Vous pouvez également utiliser des boucles +imbriquées comme dans `checkValidationLayerSupport`, car la perte en performance n'est pas capitale dans cette phase de +chargement. Lancez le code et vérifiez que votre carte graphique est capable de gérer une swap chain. Normalement +la disponibilité de la queue de présentation implique que l'extension de la swap chain est supportée. Mais soyons +tout de mêmes explicites pour cela aussi. + +## Activation des extensions du device + +L'utilisation de la swap chain nécessite l'extension `VK_KHR_swapchain`. Son activation ne requiert qu'un léger +changement à la structure de création du logical device : + +```c++ +createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); +createInfo.ppEnabledExtensionNames = deviceExtensions.data(); +``` + +Supprimez bien l'ancienne ligne `createInfo.enabledExtensionCount = 0;`. + +## Récupérer des détails à propos du support de la swap chain + +Vérifier que la swap chain est disponible n'est pas suffisant. Nous devons vérifier qu'elle est compatible avec notre +surface de fenêtre. La création de la swap chain nécessite un nombre important de paramètres, et nous devons récupérer +encore d'autres détails pour pouvoir continuer. + +Il y a trois types de propriétés que nous devrons vérifier : + +* Possibilités basiques de la surface (nombre min/max d'images dans la swap chain, hauteur/largeur min/max des images) +* Format de la surface (format des pixels, palette de couleur) +* Mode de présentation disponibles + +Nous utiliserons une structure comme celle dans `findQueueFamilies` pour contenir ces détails une fois qu'ils auront +été récupérés. Les trois catégories mentionnées plus haut se présentent sous la forme de la structure et des listes de +structures suivantes : + +```c++ +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; +``` + +Créons maintenant une nouvelle fonction `querySwapChainSupport` qui remplira cette structure : + +```c++ +SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + return details; +} +``` + +Cette section couvre la récupération des structures. Ce qu'elles signifient sera expliqué dans la section suivante. + +Commençons par les capacités basiques de la texture. Il suffit de demander ces informations et elles nous seront +fournies sous la forme d'une structure du type `VkSurfaceCapabilitiesKHR`. + +```c++ +vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); +``` + +Cette fonction requiert que le physical device et la surface de fenêtre soient passées en paramètres, car elle en +extrait ces capacités. Toutes les fonctions récupérant des capacités de la swap chain demanderont ces paramètres, +car ils en sont les composants centraux. + +La prochaine étape est de récupérer les formats de texture supportés. Comme c'est une liste de structure, cette +acquisition suit le rituel des deux étapes : + +```c++ +uint32_t formatCount; +vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + +if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); +} +``` + +Finalement, récupérer les modes de présentation supportés suit le même principe et utilise +`vkGetPhysicalDeviceSurfacePresentModesKHR` : + +```c++ +uint32_t presentModeCount; +vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + +if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); +} +``` + +Tous les détails sont dans des structures, donc étendons `isDeviceSuitable` une fois de plus et utilisons cette +fonction pour vérifier que le support de la swap chain nous correspond. Nous ne demanderons que des choses très +simples dans ce tutoriel. + +```c++ +bool swapChainAdequate = false; +if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); +} +``` + +Il est important de ne vérifier le support de la swap chain qu'après s'être assuré que l'extension est disponible. La +dernière ligne de la fonction devient donc : + +```c++ +return indices.isComplete() && extensionsSupported && swapChainAdequate; +``` + +## Choisir les bons paramètres pour la swap chain + +Si la fonction `swapChainAdequate` retourne `true` le support de la swap chain est assuré. Il existe cependant encore +plusieurs modes ayant chacun leur intérêt. Nous allons maintenant écrire quelques fonctions qui détermineront les bons +paramètres pour obtenir la swap chain la plus efficace possible. Il y a trois types de paramètres à déterminer : + +* Format de la surface (profondeur de la couleur) +* Modes de présentation (conditions de "l'échange" des images avec l'écran) +* Swap extent (résolution des images dans la swap chain) + +Pour chacun de ces paramètres nous aurons une valeur idéale que nous choisirons si elle est disponible, sinon nous +nous rabattrons sur ce qui nous restera de mieux. + +### Format de la surface + +La fonction utilisée pour déterminer ce paramètre commence ainsi. Nous lui passerons en argument le membre donnée +`formats` de la structure `SwapChainSupportDetails`. + +```c++ +VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + +} +``` + +Chaque `VkSurfaceFormatKHR` contient les données `format` et `colorSpace`. Le `format` indique les canaux de couleur +disponibles et les types qui contiennent les valeurs des gradients. Par exemple `VK_FORMAT_B8G8R8A8_SRGB` signifie que +nous stockons les canaux de couleur R, G, B et A dans cet ordre et en entiers non signés de 8 bits. `colorSpace` permet +de vérifier que le sRGB est supporté en utilisant le champ de bits `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR`. + +Pour l'espace de couleur nous utiliserons sRGB si possible, car il en résulte +[un rendu plus réaliste](http://stackoverflow.com/questions/12524623/). Le format le plus commun est +`VK_FORMAT_B8G8R8A8_SRGB`. + +Itérons dans la liste et voyons si le meilleur est disponible : + +```c++ +for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } +} +``` + +Si cette approche échoue aussi nous pourrions trier les combinaisons disponibles, mais pour rester simple nous +prendrons le premier format disponible. + +```c++ +VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; +} +``` + +### Mode de présentation + +Le mode de présentation est clairement le paramètre le plus important pour la swap chain, car il touche aux +conditions d'affichage des images à l'écran. Il existe quatre modes avec Vulkan : + +* `VK_PRESENT_MODE_IMMEDIATE_KHR` : les images émises par votre application sont directement envoyées à l'écran, ce +qui peut produire des déchirures (tearing). +* `VK_PRESENT_MODE_FIFO_KHR` : la swap chain est une file d'attente, et l'écran récupère l'image en haut de la pile +quand il est rafraîchi, alors que le programme insère ses nouvelles images à l'arrière. Si la queue est pleine le +programme doit attendre. Ce mode est très similaire à la synchronisation verticale utilisée par la plupart des jeux +vidéo modernes. L'instant durant lequel l'écran est rafraichi s'appelle l'*intervalle de rafraîchissement vertical* (vertical blank). +* `VK_PRESENT_MODE_FIFO_RELAXED_KHR` : ce mode ne diffère du précédent que si l'application est en retard et que la +queue est vide pendant le vertical blank. Au lieu d'attendre le prochain vertical blank, une image arrivant dans la +file d'attente sera immédiatement transmise à l'écran. +* `VK_PRESENT_MODE_MAILBOX_KHR` : ce mode est une autre variation du second mode. Au lieu de bloquer l'application +quand le file d'attente est pleine, les images présentes dans la queue sont simplement remplacées par de nouvelles. +Ce mode peut être utilisé pour implémenter le triple buffering, qui vous permet d'éliminer le tearing tout en réduisant +le temps de latence entre le rendu et l'affichage qu'une file d'attente implique. + +Seul `VK_PRESENT_MODE_FIFO_KHR` est toujours disponible. Nous aurons donc encore à écrire une fonction pour réaliser +un choix, car le mode que nous choisirons préférentiellement est `VK_PRESENT_MODE_MAILBOX_KHR` : + +```c++ +VkPresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) { + return VK_PRESENT_MODE_FIFO_KHR; +} +``` + +Je pense que le triple buffering est un très bon compromis. Vérifions si ce mode est disponible : + +```c++ +VkPresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} +``` + +### Le swap extent + +Il ne nous reste plus qu'une propriété, pour laquelle nous allons créer encore une autre fonction : + +```c++ +VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + +} +``` + +Le swap extent donne la résolution des images dans la swap chain et correspond quasiment toujours à la résolution de +la fenêtre que nous utilisons. L'étendue des résolutions disponibles est définie dans la +structure `VkSurfaceCapabilitiesKHR`. Vulkan nous demande de faire correspondre notre résolution à celle de la fenêtre +fournie par le membre `currentExtent`. Cependant certains gestionnaires de fenêtres nous permettent de choisir une +résolution différente, ce que nous pouvons détecter grâce aux membres `width` et `height` qui sont alors égaux à la plus +grande valeur d'un `uint32_t`. Dans ce cas nous choisirons la résolution correspondant le mieux à la taille de la +fenêtre, dans les bornes de `minImageExtent` et `maxImageExtent`. + +```c++ +#include // uint32_t +#include // std::numeric_limits +#include // std::clamp + +... + +VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + VkExtent2D actualExtent = {WIDTH, HEIGHT}; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } +} +``` + +La fonction `clamp` est utilisée ici pour limiter les valeurs `WIDTH` et `HEIGHT` entre le minimum et le +maximum supportés par l'implémentation. + +## Création de la swap chain + +Maintenant que nous avons toutes ces fonctions nous pouvons enfin acquérir toutes les informations nécessaires à la +création d'une swap chain. + +Créez une fonction `createSwapChain`. Elle commence par récupérer les résultats des fonctions précédentes. Appelez-la +depuis `initVulkan` après la création du logical device. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); +} + +void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); +} +``` + +Il nous reste une dernière chose à faire : déterminer le nombre d'images dans la swap chain. L'implémentation décide +d'un minimum nécessaire pour fonctionner : + +```c++ +uint32_t imageCount = swapChainSupport.capabilities.minImageCount; +``` + +Se contenter du minimum pose cependant un problème. Il est possible que le driver fasse attendre notre programme car il +n'a pas fini certaines opérations, ce que nous ne voulons pas. Il est recommandé d'utiliser au moins une image de plus +que ce minimum : + +```c++ +uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; +``` + +Il nous faut également prendre en compte le maximum d'images supportées par l'implémentation. La valeur `0` signifie +qu'il n'y a pas de maximum autre que la mémoire. + +```c++ +if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; +} +``` + +Comme la tradition le veut avec Vulkan, la création d'une swap chain nécessite de remplir une grande structure. Elle +commence de manière familière : + +```c++ +VkSwapchainCreateInfoKHR createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; +createInfo.surface = surface; +``` + +Après avoir indiqué la surface à laquelle la swap chain doit être liée, les détails sur les images de la swap chain +doivent être fournis : + +```c++ +createInfo.minImageCount = imageCount; +createInfo.imageFormat = surfaceFormat.format; +createInfo.imageColorSpace = surfaceFormat.colorSpace; +createInfo.imageExtent = extent; +createInfo.imageArrayLayers = 1; +createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +``` + +Le membre `imageArrayLayers` indique le nombre de couches que chaque image possède. Ce sera toujours `1` sauf si vous +développez une application stéréoscopique 3D. Le champ de bits `imageUsage` spécifie le type d'opérations que nous +appliquerons aux images de la swap chain. Dans ce tutoriel nous effectuerons un rendu directement sur les images, +nous les utiliserons donc comme *color attachement*. Vous voudrez peut-être travailler sur une image séparée pour +pouvoir appliquer des effets en post-processing. Dans ce cas vous devrez utiliser une valeur comme +`VK_IMAGE_USAGE_TRANSFER_DST_BIT` à la place et utiliser une opération de transfert de mémoire pour placer le +résultat final dans une image de la swap chain. + +```c++ +QueueFamilyIndices indices = findQueueFamilies(physicalDevice); +uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + +if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; +} else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; // Optionnel + createInfo.pQueueFamilyIndices = nullptr; // Optionnel +} +``` + +Nous devons ensuite indiquer comment les images de la swap chain seront utilisées dans le cas où plusieurs queues +seront à l'origine d'opérations. Cela sera le cas si la queue des graphismes n'est pas la même que la queue de +présentation. Nous devrons alors dessiner avec la graphics queue puis fournir l'image à la presentation queue. Il +existe deux manières de gérer les images accédées par plusieurs queues : + +* `VK_SHARING_MODE_EXCLUSIVE` : une image n'est accesible que par une queue à la fois et sa gestion doit être +explicitement transférée à une autre queue pour pouvoir être utilisée. Cette option offre le maximum de performances. +* `VK_SHARING_MODE_CONCURRENT` : les images peuvent être simplement utilisées par différentes queue families. + +Si nous avons deux queues différentes, nous utiliserons le mode concurrent pour éviter d'ajouter un chapitre sur la +possession des ressources, car cela nécessite des concepts que nous ne pourrons comprendre correctement que plus tard. +Le mode concurrent vous demande de spécifier à l'avance les queues qui partageront les images en utilisant les +paramètres `queueFamilyIndexCount` et `pQueueFamilyIndices`. Si les graphics queue et presentation queue sont les +mêmes, ce qui est le cas sur la plupart des cartes graphiques, nous devons rester sur le mode exclusif car le mode +concurrent requiert au moins deux queues différentes. + +```c++ +createInfo.preTransform = swapChainSupport.capabilities.currentTransform; +``` + +Nous pouvons spécifier une transformation à appliquer aux images quand elles entrent dans la swap chain si cela est +supporté (à vérifier avec `supportedTransforms` dans `capabilities`), comme par exemple une rotation de 90 degrés ou +une symétrie verticale. Si vous ne voulez pas de transformation, spécifiez la transformation actuelle. + +```c++ +createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; +``` + +Le champ `compositeAlpha` indique si le canal alpha doit être utilisé pour mélanger les couleurs avec celles des autres +fenêtres. Vous voudrez quasiment tout le temps ignorer cela, et indiquer `VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR` : + +```c++ +createInfo.presentMode = presentMode; +createInfo.clipped = VK_TRUE; +``` + +Le membre `presentMode` est assez simple. Si le membre `clipped` est activé avec `VK_TRUE` alors les couleurs des +pixels masqués par d'autres fenêtres seront ignorées. Si vous n'avez pas un besoin particulier de lire ces +informations, vous obtiendrez de meilleures performances en activant ce mode. + +```c++ +createInfo.oldSwapchain = VK_NULL_HANDLE; +``` + +Il nous reste un dernier champ, `oldSwapchain`. Il est possible avec Vulkan que la swap chain devienne +invalide ou mal adaptée pendant que votre application tourne, par exemple parce que la fenêtre a été redimensionnée. +Dans ce cas la swap chain doit être intégralement recréée et une référence à l'ancienne swap chain doit être fournie. +C'est un sujet compliqué que nous aborderons [dans un chapitre futur](!fr/Dessiner_un_triangle/Recréation_de_la_swap_chain). +Pour le moment, considérons que nous ne devrons jamais créer qu'une swap chain. + +Ajoutez un membre donnée pour stocker l'objet `VkSwapchainKHR` : + +```c++ +VkSwapchainKHR swapChain; +``` + +Créer la swap chain ne se résume plus qu'à appeler `vkCreateSwapchainKHR` : + +```c++ +if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("échec de la création de la swap chain!"); +} +``` + +Les paramètres sont le logical device, la structure contenant les informations, l'allocateur optionnel et la variable +contenant la référence à la swap chain. Cet objet devra être explicitement détruit à l'aide de la fonction +`vkDestroySwapchainKHR` avant de détruire le logical device : + +```c++ +void cleanup() { + vkDestroySwapchainKHR(device, swapChain, nullptr); + ... +} +``` + +Lancez maintenant l'application et contemplez la création de la swap chain! Si vous obtenez une erreur de violation +d'accès dans `vkCreateSwapchainKHR` ou voyez un message comme `Failed to find 'vkGetInstanceProcAddress' in layer +SteamOverlayVulkanLayer.ddl`, allez voir [la FAQ à propos de la layer Steam](!fr/FAQ). + +Essayez de retirer la ligne `createInfo.imageExtent = extent;` avec les validation layers actives. Vous verrez que +l'une d'entre elles verra l'erreur et un message vous sera envoyé : + +![](/images/swap_chain_validation_layer.png) + +## Récupérer les images de la swap chain + +La swap chain est enfin créée. Il nous faut maintenant récupérer les références aux `VkImage` dans la swap +chain. Nous les utiliserons pour l'affichage et dans les chapitres suivants. Ajoutez un membre donnée pour les +stocker : + +```c++ +std::vector swapChainImages; +``` + +Ces images ont été créées par l'implémentation avec la swap chain et elles seront automatiquement supprimées avec la +destruction de la swap chain, nous n'aurons donc rien à rajouter dans la fonction `cleanup`. + +Ajoutons le code nécessaire à la récupération des références à la fin de `createSwapChain`, juste après l'appel à +`vkCreateSwapchainKHR`. Comme notre logique n'a au final informé Vulkan que d'un minimum pour le nombre d'images dans la +swap chain, nous devons nous enquérir du nombre d'images avant de redimensionner le conteneur. + +```c++ +vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); +swapChainImages.resize(imageCount); +vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); +``` + +Une dernière chose : gardez dans des variables le format et le nombre d'images de la swap chain, nous en aurons +besoin dans de futurs chapitres. + +```c++ +VkSwapchainKHR swapChain; +std::vector swapChainImages; +VkFormat swapChainImageFormat; +VkExtent2D swapChainExtent; + +... + +swapChainImageFormat = surfaceFormat.format; +swapChainExtent = extent; +``` + +Nous avons maintenant un ensemble d'images sur lesquelles nous pouvons travailler et qui peuvent être présentées pour +être affichées. Dans le prochain chapitre nous verrons comment utiliser ces images comme des cibles de rendu, +puis nous verrons le pipeline graphique et les commandes d'affichage! + +[Code C++](/code/06_swap_chain_creation.cpp) diff --git "a/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/02_Image_views.md" "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/02_Image_views.md" new file mode 100644 index 00000000..f1f7a262 --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/02_Image_views.md" @@ -0,0 +1,118 @@ +Quelque soit la `VkImage` que nous voulons utiliser, dont celles de la swap chain, nous devons en créer une +`VkImageView` pour la manipuler. Cette image view correspond assez litéralement à une vue dans l'image. Elle décrit +l'accès à l'image et les parties de l'image à accéder. Par exemple elle indique si elle doit être traitée comme une +texture 2D pour la profondeur sans aucun niveau de mipmapping. + +Dans ce chapitre nous écrirons une fonction `createImageViews` pour créer une image view basique pour chacune des +images dans la swap chain, pour que nous puissions les utiliser comme cibles de couleur. + +Ajoutez d'abord un membre donnée pour y stocker une image view : + +```c++ +std::vector swapChainImageViews; +``` + +Créez la fonction `createImageViews` et appelez-la juste après la création de la swap chain. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); +} + +void createImageViews() { + +} +``` + +Nous devons d'abord redimensionner la liste pour pouvoir y mettre toutes les image views que nous créerons : + +```c++ +void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + +} +``` + +Créez ensuite la boucle qui parcourra toutes les images de la swap chain. + +```c++ +for (size_t i = 0; i < swapChainImages.size(); i++) { + +} +``` + +Les paramètres pour la création d'image views se spécifient dans la structure `VkImageViewCreateInfo`. Les deux +premiers paramètres sont assez simples : + +```c++ +VkImageViewCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; +createInfo.image = swapChainImages[i]; +``` + +Les champs `viewType` et `format` indiquent la manière dont les images doivent être interprétées. Le paramètre +`viewType` permet de traiter les images comme des textures 1D, 2D, 3D ou cube map. + +```c++ +createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; +createInfo.format = swapChainImageFormat; +``` + +Le champ `components` vous permet d'altérer les canaux de couleur. Par exemple, vous pouvez envoyer tous les +canaux au canal rouge pour obtenir une texture monochrome. Vous pouvez aussi donner les valeurs constantes `0` ou `1` +à un canal. Dans notre cas nous garderons les paramètres par défaut. + +```c++ +createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; +createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; +createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; +createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; +``` + +Le champ `subresourceRange` décrit l'utilisation de l'image et indique quelles parties de l'image devraient être +accédées. Notre image sera utilisée comme cible de couleur et n'aura ni mipmapping ni plusieurs couches. + +```c++ +createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +createInfo.subresourceRange.baseMipLevel = 0; +createInfo.subresourceRange.levelCount = 1; +createInfo.subresourceRange.baseArrayLayer = 0; +createInfo.subresourceRange.layerCount = 1; +``` + +Si vous travailliez sur une application 3D stéréoscopique, vous devrez alors créer une swap chain avec plusieurs +couches. Vous pourriez alors créer plusieurs image views pour chaque image. Elles représenteront ce qui sera affiché +pour l'Å“il gauche et pour l'Å“il droit. + +Créer l'image view ne se résume plus qu'à appeler `vkCreateImageView` : + +```c++ +if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("échec de la création d'une image view!"); +} +``` + +À la différence des images, nous avons créé les image views explicitement et devons donc les détruire de la même +manière, ce que nous faisons à l'aide d'une boucle : + +```c++ +void cleanup() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + ... +} +``` + +Une image view est suffisante pour commencer à utiliser une image comme une texture, mais pas pour que l'image soit +utilisée comme cible d'affichage. Pour cela nous avons encore une étape, appelée framebuffer. Mais nous devons +d'abord mettre en place le pipeline graphique. + +[Code C++](/code/07_image_views.cpp) diff --git a/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/00_Introduction.md b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/00_Introduction.md new file mode 100644 index 00000000..7ff76a6a --- /dev/null +++ b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/00_Introduction.md @@ -0,0 +1,84 @@ +Dans les chapitres qui viennent nous allons configurer une pipeline graphique pour qu'elle affiche notre premier +triangle. La pipeline graphique est l'ensemble des opérations qui prennent les vertices et les textures de vos +éléments et les utilisent pour en faire des pixels sur les cibles d'affichage. Un résumé simplifié ressemble à ceci : + +![](/images/vulkan_simplified_pipeline.svg) + +L'_input assembler_ collecte les données des sommets à partir des buffers que vous avez mis en place, et peut aussi +utiliser un _index buffer_ pour répéter certains éléments sans avoir à stocker deux fois les mêmes données dans un +buffer. + +Le _vertex shader_ est exécuté pour chaque sommet et leur applique en général des transformations pour que leurs +coordonnées passent de l'espace du modèle (model space) à l'espace de l'écran (screen space). Il fournit ensuite des +données à la suite de la pipeline. + +Les _tesselation shaders_ permettent de subdiviser la géométrie selon des règles paramétrables afin d'améliorer la +qualité du rendu. Ce procédé est notamment utilisé pour que des surface comme les murs de briques ou les escaliers +aient l'air moins plats lorsque l'on s'en approche. + +Le _geometry shader_ est invoqué pour chaque primitive (triangle, ligne, points...) et peut les détruire ou en créer +de nouvelles, du même type ou non. Ce travail est similaire au tesselation shader tout en étant beaucoup plus +flexible. Il n'est cependant pas beaucoup utilisé à cause de performances assez moyennes sur les cartes graphiques +(avec comme exception les GPUs intégrés d'Intel). + +La _rasterization_ transforme les primitives en _fragments_. Ce sont les pixels auxquels les primitives correspondent +sur le framebuffer. Tout fragment en dehors de l'écran est abandonné. Les attributs sortant du vertex shader +sont interpolés lorsqu'ils sont donnés aux étapes suivantes. Les fragments cachés par d'autres fragments sont aussi +quasiment toujours éliminés grâce au test de profondeur (depth testing). + +Le _fragment shader_ est invoqué pour chaque fragment restant et détermine à quel(s) framebuffer(s) le fragment +est envoyé, et quelles données y sont inscrites. Il réalise ce travail à l'aide des données interpolées émises par le +vertex shader, ce qui inclut souvent des coordonnées de texture et des normales pour réaliser des calculs d'éclairage. + +Le _color blending_ applique des opérations pour mixer différents fragments correspondant à un même pixel sur le +framebuffer. Les fragments peuvent remplacer les valeurs des autres, s'additionner ou se mélanger selon les +paramètres de transparence (ou plus correctement de translucidité, en anglais translucency). + +Les étapes écrites en vert sur le diagramme s'appellent _fixed-function stages_ (étapes à fonction fixée). Il est +possible de modifier des paramètres influençant les calculs, mais pas de modifier les calculs eux-mêmes. + +Les étapes colorées en orange sont programmables, ce qui signifie que vous pouvez charger votre propre code dans la +carte graphique pour y appliquer exactement ce que vous voulez. Cela vous permet par exemple d'utiliser les fragment +shaders pour implémenter n'importe quoi, de l'utilisation de textures et d'éclairage jusqu'au _ray tracing_. Ces +programmes tournent sur de nombreux coeurs simultanément pour y traiter de nombreuses données en parallèle. + +Si vous avez utilisé d'anciens APIs comme OpenGL ou Direct3D, vous êtes habitués à pouvoir changer un quelconque +paramètre de la pipeline à tout moment, avec des fonctions comme `glBlendFunc` ou `OMSSetBlendState`. Cela n'est plus +possible avec Vulkan. La pipeline graphique y est quasiment fixée, et vous devrez en recréer une complètement si +vous voulez changer de shader, y attacher différents framebuffers ou changer le color blending. Devoir créer une +pipeline graphique pour chacune des combinaisons dont vous aurez besoin tout au long du programme représente un gros +travail, mais permet au driver d'optimiser beaucoup mieux l'exécution des tâches car il sait à l'avance ce que la carte +graphique aura à faire. + +Certaines étapes programmables sont optionnelles selon ce que vous voulez faire. Par exemple la tesselation et le +geometry shader peuvent être désactivés. Si vous n'êtes intéressé que par les valeurs de profondeur vous pouvez +désactiver le fragment shader, ce qui est utile pour les [shadow maps](https://en.wikipedia.org/wiki/Shadow_mapping). + +Dans le prochain chapitre nous allons d'abord créer deux étapes nécessaires à l'affichage d'un triangle à l'écran : +le vertex shader et le fragment shader. Les étapes à fonction fixée seront mises en place dans le chapitre suivant. +La dernière préparation nécessaire à la mise en place de la pipeline graphique Vulkan sera de fournir les framebuffers +d'entrée et de sortie. + +Créez la fonction `createGraphicsPipeline` et appelez-la depuis `initVulkan` après `createImageViews`. Nous +travaillerons sur cette fonction dans les chapitres suivants. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); +} + +... + +void createGraphicsPipeline() { + +} +``` + +[Code C++](/code/08_graphics_pipeline.cpp) diff --git a/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/01_Modules_shaders.md b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/01_Modules_shaders.md new file mode 100644 index 00000000..53713cb4 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/01_Modules_shaders.md @@ -0,0 +1,432 @@ +À la différence d'anciens APIs, le code des shaders doit être fourni à Vulkan sous la forme de bytecode et non sous une +forme facilement compréhensible par l'homme, comme [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) ou +[HLSL](https://en.wikipedia.org/wiki/High-Level_Shading_Language). Ce format est appelé +[SPIR-V](https://www.khronos.org/spir) et est conçu pour fonctionner avec Vulkan et OpenCL (deux APIs de Khronos). Ce +format peut servir à écrire du code éxécuté sur la carte graphique pour les graphismes et pour le calcul, mais nous +nous concentrerons sur la pipeline graphique dans ce tutoriel. + +L'avantage d'un tel format est que le compilateur spécifique de la carte graphique a beaucoup moins de travail +d'interprétation. L'expérience a en effet montré qu'avec les syntaxes compréhensibles par l'homme, certains +compilateurs étaient très laxistes par rapport à la spécification qui leur était fournie. Si vous écriviez du code +complexe, il pouvait être accepté par l'un et pas par l'autre, ou pire s'éxécuter différemment. Avec le format de +plus bas niveau qu'est SPIR-V, ces problèmes seront normalement éliminés. + +Cela ne veut cependant pas dire que nous devrons écrire ces bytecodes à la main. Khronos fournit même un +compilateur transformant GLSL en SPIR-V. Ce compilateur standard vérifiera que votre code correspond à la spécification. +Vous pouvez également l'inclure comme une bibliothèque pour produire du SPIR-V au runtime, mais nous ne ferons pas cela dans ce tutoriel. +Le compilateur est fourni avec le SDK et s'appelle `glslangValidator`, mais nous allons utiliser un autre compilateur +nommé `glslc`, écrit par Google. L'avantage de ce dernier est qu'il utilise le même format d'options que GCC ou Clang, +et inclu quelques fonctionnalités supplémentaires comme les *includes*. Les deux compilateurs sont fournis dans le SDK, +vous n'avez donc rien de plus à télécharger. + +GLSL est un langage possédant une syntaxe proche du C. Les programmes y ont une fonction `main` invoquée pour chaque +objet à traiter. Plutôt que d'utiliser des paramètres et des valeurs de retour, GLSL utilise des variables globales +pour les entrées et sorties des invocations. Le langage possède des fonctionnalités avancées pour aider le travail +avec les mathématiques nécessaires aux graphismes, avec par exemple des vecteurs, des matrices et des fonctions pour +les traiter. On y trouve des fonctions pour réaliser des produits vectoriels ou des réflexions d'un vecteurs par +rapport à un autre. Le type pour les vecteurs s'appelle `vec` et est suivi d'un nombre indiquant le nombre d'éléments, +par exemple `vec3`. On peut accéder à ses données comme des membres avec par exemple `.y`, mais aussi créer de nouveaux +vecteurs avec plusieurs indications, par exemple `vec3(1.0, 2.0, 3.0).xz` qui crée un `vec2` égal à `(1.0, 3.0)`. +Leurs constructeurs peuvent aussi être des combinaisons de vecteurs et de valeurs. Par exemple il est possible de +créer un `vec3` ainsi : `vec3(vec2(1.0, 2.0), 3.0)`. + +Comme nous l'avons dit au chapitre précédent, nous devrons écrire un vertex shader et un fragment shader pour pouvoir +afficher un triangle à l'écran. Les deux prochaines sections couvrirons ce travail, puis nous verrons comment créer +des bytecodes SPIR-V avec ce code. + +## Le vertex shader + +Le vertex shader traite chaque sommet envoyé depuis le programme C++. Il récupère des données telles la position, la +normale, la couleur ou les coordonnées de texture. Ses sorties sont la position du somment dans l'espace de l'écran et +les autres attributs qui doivent être fournies au reste de la pipeline, comme la couleur ou les coordonnées de texture. +Ces valeurs seront interpolées lors de la rasterization afin de produire un dégradé continu. Ainsi les invocation du +fragment shader recevrons des vecteurs dégradés entre deux sommets. + +Une _clip coordinate_ est un vecteur à quatre éléments émis par le vertex shader. Il est ensuite transformé en une +_normalized screen coordinate_ en divisant ses trois premiers composants par le quatrième. Ces coordonnées sont des +[coordonnées homogènes](https://fr.wikipedia.org/wiki/Coordonn%C3%A9es_homog%C3%A8nes) qui permettent d'accéder au frambuffer +grâce à un repère de [-1, 1] par [-1, 1]. Il ressemble à cela : + +![](/images/normalized_device_coordinates.svg) + +Vous devriez déjà être familier de ces notions si vous avez déjà utilisé des graphismes 3D. Si vous avez utilisé +OpenGL avant vous vous rendrez compte que l'axe Y est maintenenant inversé et que l'axe Z va de 0 à 1, comme Direct3D. + +Pour notre premier triangle nous n'appliquerons aucune transformation, nous nous contenterons de spécifier +directement les coordonnées des trois sommets pour créer la forme suivante : + +![](/images/triangle_coordinates.svg) + +Nous pouvons directement émettre ces coordonnées en mettant leur quatrième composant à 1 de telle sorte que la +division ne change pas les valeurs. + +Ces coordonnées devraient normalement être stockées dans un vertex buffer, mais sa création et son remplissage ne +sont pas des opérations triviales. J'ai donc décidé de retarder ce sujet afin d'obtenir plus rapidement un résultat +visible à l'écran. Nous ferons ainsi quelque chose de peu orthodoxe en attendant : inclure les coordonnées directement +dans le vertex shader. Son code ressemble donc à ceci : + +```glsl +#version 450 + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); +} +``` + +La fonction `main` est invoquée pour chaque sommet. La variable prédéfinie `gl_VertexIndex` contient l'index du +sommet à l'origine de l'invocation du `main`. Elle est en général utilisée comme index dans le vertex buffer, mais nous +l'emploierons pour déterminer la coordonnée à émettre. Cette coordonnée est extraite d'un tableau prédéfini à trois +entrées, et est combinée avec un `z` à 0.0 et un `w` à 1.0 pour faire de la division une identité. La variable +prédéfinie `gl_Position` fonctionne comme sortie pour les coordonnées. + +## Le fragment shader + +Le triangle formé par les positions émises par le vertex shader remplit un certain nombre de fragments. Le fragment +shader est invoqué pour chacun d'entre eux et produit une couleur et une profondeur, qu'il envoie à un ou plusieurs +framebuffer(s). Un fragment shader colorant tout en rouge est ainsi écrit : + +```glsl +#version 450 + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(1.0, 0.0, 0.0, 1.0); +} +``` + +Le `main` est appelé pour chaque fragment de la même manière que le vertex shader est appelé pour chaque sommet. Les +couleurs sont des vecteurs de quatre composants : R, G, B et le canal alpha. Les valeurs doivent être incluses dans +[0, 1]. Au contraire de `gl_Position`, il n'y a pas (plus exactement il n'y a plus) de variable prédéfinie dans +laquelle entrer la valeur de la couleur. Vous devrez spécifier votre propre variable pour contenir la couleur du +fragment, où `layout(location = 0)` indique l'index du framebuffer où la couleur sera écrite. Ici, la couleur rouge est +écrite dans `outColor` liée au seul et unique premier framebuffer. + +## Une couleur pour chaque vertex + +Afficher ce que vous voyez sur cette image ne serait pas plus intéressant qu'un triangle entièrement rouge? + +![](/images/triangle_coordinates_colors.png) + +Nous devons pour cela faire quelques petits changements aux deux shaders. Spécifions d'abord une couleur distincte +pour chaque sommet. Ces couleurs seront inscrites dans le vertex shader de la même manière que les positions : + +```glsl +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); +``` + +Nous devons maintenant passer ces couleurs au fragment shader afin qu'il puisse émettre des valeurs interpolées et +dégradées au framebuffer. Ajoutez une variable de sortie pour la couleur dans le vertex shader et donnez lui une +valeur dans le `main`: + +```glsl +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} +``` + +Nous devons ensuite ajouter l'entrée correspondante dans le fragment shader, dont la valeur sera l'interpolation +correspondant à la position du fragment pour lequel le shader sera invoqué : + +```glsl +layout(location = 0) in vec3 fragColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} +``` + +Les deux variables n'ont pas nécessairement le même nom, elles seront reliées selon l'index fourni dans la directive +`location`. La fonction `main` doit être modifiée pour émettre une couleur possédant un canal alpha. Le résultat +montré dans l'image précédente est dû à l'interpolation réalisée lors de la rasterization. + +## Compilation des shaders + +Créez un dossier `shaders` à la racine de votre projet, puis enregistrez le vertex shader dans un fichier appelé +`shader.vert` et le fragment shader dans un fichier appelé `shader.frag`. Les shaders en GLSL n'ont pas d'extension +officielle mais celles-ci correspondent à l'usage communément accepté. + +Le contenu de `shader.vert` devrait être: + +```glsl +#version 450 + +out gl_PerVertex { + vec4 gl_Position; +}; + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} +``` + +Et `shader.frag` devrait contenir : + +```glsl +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} +``` + +Nous allons maintenant compiler ces shaders en bytecode SPIR-V à l'aide du programme `glslc`. + +**Windows** + +Créez un fichier `compile.bat` et copiez ceci dedans : + +```bash +C:/VulkanSDK/x.x.x.x/Bin/glslc.exe shader.vert -o vert.spv +C:/VulkanSDK/x.x.x.x/Bin/glslc.exe shader.frag -o frag.spv +pause +``` + +Corrigez le chemin vers `glslc.exe` pour que le .bat pointe effectivement là où le vôtre se trouve. +Double-cliquez pour lancer ce script. + +**Linux** + +Créez un fichier `compile.sh` et copiez ceci dedans : + +```bash +/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.vert -o vert.spv +/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.frag -o frag.spv +``` + +Corrigez le chemin menant au `glslc` pour qu'il pointe là où il est. Rendez le script exécutable avec la +commande `chmod +x compile.sh` et lancez-le. + +**Fin des instructions spécifiques** + +Ces deux commandes instruisent le compilateur de lire le code GLSL source contenu dans un fichier et d'écrire +le bytecode SPIR-V dans un fichier grâce à l'option `-o` (output). + +Si votre shader contient une erreur de syntaxe le compilateur vous indiquera le problème et la ligne à laquelle il +apparait. Essayez de retirer un point-virgule et voyez l'efficacité du debogueur. Essayez également de voir les +arguments supportés. Il est possible de le forcer à émettre le bytecode sous un format compréhensible permettant de +voir exactement ce que le shader fait et quelles optimisations le compilateur y a réalisées. + +La compilation des shaders en ligne de commande est l'une des options les plus simples et les plus évidentes. C'est ce +que nous utiliserons dans ce tutoriel. Sachez qu'il est également possible de compiler les shaders depuis votre code. Le +SDK inclue la librairie [libshaderc](https://github.com/google/shaderc) , qui permet de compiler le GLSL en SPIR-V +depuis le programme C++. + +## Charger un shader + +Maintenant que vous pouvez créer des shaders SPIR-V il est grand temps de les charger dans le programme et de les +intégrer à la pipeline graphique. Nous allons d'abord écrire une fonction qui réalisera le chargement des données +binaires à partir des fichiers. + +```c++ +#include + +... + +static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error(std::string {"échec de l'ouverture du fichier "} + filename + "!"); + } +} +``` + +La fonction `readFile` lira tous les octets du fichier qu'on lui indique et les retournera dans un `vector` de +caractères servant ici d'octets. L'ouverture du fichier se fait avec deux paramètres particuliers : +* `ate` : permet de commencer la lecture à la fin du fichier +* `binary` : indique que le fichier doit être lu comme des octets et que ceux-ci ne doivent pas être formatés + +Commencer la lecture à la fin permet d'utiliser la position du pointeur comme indicateur de la taille totale du +fichier et nous pouvons ainsi allouer un stockage suffisant : + +```c++ +size_t fileSize = (size_t) file.tellg(); +std::vector buffer(fileSize); +``` +Après cela nous revenons au début du fichier et lisons tous les octets d'un coup : + +```c++ +file.seekg(0); +file.read(buffer.data(), fileSize); +``` + +Nous pouvons enfin fermer le fichier et retourner les octets : + +```c++ +file.close(); + +return buffer; +``` + +Appelons maintenant cette fonction depuis `createGraphicsPipeline` pour charger les bytecodes des deux shaders : + +```c++ +void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); +} +``` + +Assurez-vous que les shaders soient correctement chargés en affichant la taille des fichiers lus depuis votre +programme puis en comparez ces valeurs à la taille des fichiers indiquées par l'OS. Notez que le code n'a pas besoin +d'avoir un caractère nul en fin de chaîne car nous indiquerons à Vulkan sa taille exacte. + +## Créer des modules shader + +Avant de passer ce code à la pipeline nous devons en faire un `VkShaderModule`. Créez pour cela une fonction +`createShaderModule`. + +```c++ +VkShaderModule createShaderModule(const std::vector& code) { + +} +``` + +Cette fonction prendra comme paramètre le buffer contenant le bytecode et créera un `VkShaderModule` avec ce code. + +La création d'un module shader est très simple. Nous avons juste à indiquer un pointeur vers le buffer et la taille +de ce buffer. Ces informations seront inscrites dans la structure `VkShaderModuleCreatInfo`. Le seul problème est que +la taille doit être donnée en octets mais le pointeur sur le code est du type `uint32_t` et non du type `char`. Nous +devrons donc utiliser `reinterpet_cast` sur notre pointeur. Cet opérateur de conversion nécessite que les données +aient un alignement compatible avec `uint32_t`. Heuresement pour nous l'objet allocateur de la classe `std::vector` +s'assure que les données satisfont le pire cas d'alignement. + +```c++ +VkShaderModuleCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; +createInfo.codeSize = code.size(); +createInfo.pCode = reinterpret_cast(code.data()); +``` + +Le `VkShaderModule` peut alors être créé en appelant la fonction `vkCreateShaderModule` : + + +```c++ +VkShaderModule shaderModule; +if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("échec de la création d'un module shader!"); +} +``` + +Les paramètres sont les mêmes que pour la création des objets précédents : le logical device, le pointeur sur la +structure avec les informations, le pointeur vers l'allocateur optionnnel et la référence à l'objet créé. Le buffer +contenant le code peut être libéré immédiatement après l'appel. Retournez enfin le shader module créé : + +```c++ +return shaderModule; +``` + +Les modules shaders ne sont au fond qu'une fine couche autour du byte code chargé depuis les fichiers. Au moment de la +création de la pipeline, les codes des shaders sont compilés et mis sur la carte. Nous pouvons donc détruire les modules +dès que la pipeline est crée. Nous en ferons donc des variables locales à la fonction `createGraphicsPipeline` : + +```c++ +void createGraphicsPipeline() { + auto vertShaderModule = createShaderModule(vertShaderCode); + fragShaderModule = createShaderModule(fragShaderCode); + + vertShaderModule = createShaderModule(vertShaderCode); + fragShaderModule = createShaderModule(fragShaderCode); +``` + +Ils doivent être libérés une fois que la pipeline est créée, juste avant que `createGraphicsPipeline` ne retourne. +Ajoutez ceci à la fin de la fonction : + + +```c++ + ... + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); +} +``` + +Le reste du code de ce chapitre sera ajouté entre les deux parties de la fonction présentés ci-dessus. + +## Création des étapes shader + +Nous devons assigner une étape shader aux modules que nous avons crées. Nous allons utiliser une structure du type +`VkPipelineShaderStageCreateInfo` pour cela. + +Nous allons d'abord remplir cette structure pour le vertex shader, une fois de plus dans la fonction +`createGraphicsPipeline`. + +```c++ +VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; +vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; +vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; +``` + +La première étape, sans compter le membre `sType`, consiste à dire à Vulkan à quelle étape le shader sera utilisé. Il +existe une valeur d'énumération pour chacune des étapes possibles décrites dans le chapitre précédent. + +```c++ +vertShaderStageInfo.module = vertShaderModule; +vertShaderStageInfo.pName = "main"; +``` + +Les deux membres suivants indiquent le module contenant le code et la fonction à invoquer en *entrypoint*. Il est donc +possible de combiner plusieurs fragment shaders dans un seul module et de les différencier à l'aide de leurs points +d'entrée. Nous nous contenterons du `main` standard. + +Il existe un autre membre, celui-ci optionnel, appelé `pSpecializationInfo`, que nous n'utiliserons pas mais qu'il +est intéressant d'évoquer. Il vous permet de donner des valeurs à des constantes présentes dans le code du shader. +Vous pouvez ainsi configurer le comportement d'un shader lors de la création de la pipeline, ce qui est plus efficace +que de le faire pendant l'affichage, car alors le compilateur (qui n'a toujours pas été invoqué!) peut éliminer des +pants entiers de code sous un `if` vérifiant la valeur d'une constante ainsi configurée. Si vous n'avez aucune +constante mettez ce paramètre à `nullptr`. + +Modifier la structure pour qu'elle corresponde au fragment shader est très simple : + +```c++ +VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; +fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; +fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; +fragShaderStageInfo.module = fragShaderModule; +fragShaderStageInfo.pName = "main"; +``` + +Intégrez ces deux valeurs dans un tableau que nous utiliserons plus tard et vous aurez fini ce chapitre! + +```c++ +VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; +``` + +C'est tout ce que nous dirons sur les étapes programmables de la pipeline. Dans le prochain chapitre nous verrons les +étapes à fonction fixée. + +[Code C++](/code/09_shader_modules.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git "a/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/02_Fonctions_fix\303\251es.md" "b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/02_Fonctions_fix\303\251es.md" new file mode 100644 index 00000000..8cf9da12 --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/02_Fonctions_fix\303\251es.md" @@ -0,0 +1,380 @@ +Les anciens APIs définissaient des configurations par défaut pour toutes les étapes à fonction fixée de la pipeline +graphique. Avec Vulkan vous devez être explicite dans ce domaine également et devrez donc configurer la fonction de +mélange par exemple. Dans ce chapitre nous remplirons toutes les structures nécessaires à la configuration des étapes à +fonction fixée. + +## Entrée des sommets + +La structure `VkPipelineVertexInputStateCreateInfo` décrit le format des sommets envoyés au vertex shader. Elle +fait cela de deux manières : + +* Liens (bindings) : espace entre les données et information sur ces données; sont-elles par sommet ou par instance? +(voyez [l'instanciation](https://en.wikipedia.org/wiki/Geometry_instancing)) +* Descriptions d'attributs : types d'attributs passés au vertex shader, de quels bindings les charger et avec quel +décalage entre eux. + +Dans la mesure où nous avons écrit les coordonnées directement dans le vertex shader, nous remplirons cette structure +en indiquant qu'il n'y a aucune donnée à charger. Nous y reviendrons dans le chapitre sur les vertex buffers. + +```c++ +VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; +vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; +vertexInputInfo.vertexBindingDescriptionCount = 0; +vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optionnel +vertexInputInfo.vertexAttributeDescriptionCount = 0; +vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optionnel +``` + +Les membres `pVertexBindingDescriptions` et `pVertexAttributeDescriptions` pointent vers un tableau de structures +décrivant les détails du chargement des données des sommets. Ajoutez cette structure à la fonction +`createGraphicsPipeline` juste après le tableau `shaderStages`. + +## Input assembly + +La structure `VkPipelineInputAssemblyStateCreateInfo` décrit la nature de la géométrie voulue quand les sommets sont +reliés, et permet d'activer ou non la réévaluation des vertices. La première information est décrite dans le membre +`topology` et peut prendre ces valeurs : + +* `VK_PRIMITIVE_TOPOLOGY_POINT_LIST` : chaque sommet est un point +* `VK_PRIMITIVE_TOPOLOGY_LINE_LIST` : dessine une ligne liant deux sommet en n'utilisant ces derniers qu'une seule fois +* `VK_PRIMITIVE_TOPOLOGY_LINE_STRIP` : le dernier sommet de chaque ligne est utilisée comme premier sommet +pour la ligne suivante +* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST` : dessine un triangle en utilisant trois sommets, sans en réutiliser pour le +triangle suivant +* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP ` : le deuxième et troisième sommets sont utilisées comme les deux premiers +pour le triangle suivant + +Les sommets sont normalement chargés séquentiellement depuis le vertex buffer. Avec un _element buffer_ vous pouvez +cependant choisir vous-même les indices à charger. Vous pouvez ainsi réaliser des optimisations, comme n'utiliser +une combinaison de sommet qu'une seule fois au lieu de d'avoir les mêmes données plusieurs fois dans le buffer. Si +vous mettez le membre `primitiveRestartEnable` à la valeur `VK_TRUE`, il devient alors possible d'interrompre les +liaisons des vertices pour les modes `_STRIP` en utilisant l'index spécial `0xFFFF` ou `0xFFFFFFFF`. + +Nous n'afficherons que des triangles dans ce tutoriel, nous nous contenterons donc de remplir la structure de +cette manière : + +```c++ +VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; +inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; +inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; +inputAssembly.primitiveRestartEnable = VK_FALSE; +``` + +## Viewports et ciseaux + +Un viewport décrit simplement la région d'un framebuffer sur laquelle le rendu sera effectué. Il couvrira dans la +pratique quasiment toujours la totalité du framebuffer, et ce sera le cas dans ce tutoriel. + +```c++ +VkViewport viewport{}; +viewport.x = 0.0f; +viewport.y = 0.0f; +viewport.width = (float) swapChainExtent.width; +viewport.height = (float) swapChainExtent.height; +viewport.minDepth = 0.0f; +viewport.maxDepth = 1.0f; +``` + +N'oubliez pas que la taille des images de la swap chain peut différer des macros `WIDTH` et `HEIGHT`. Les images de +la swap chain seront plus tard les framebuffers sur lesquels la pipeline opérera, ce que nous devons prendre en compte +en donnant les dimensions dynamiquement acquises. + +Les valeurs `minDepth` et `maxDepth` indiquent l'étendue des valeurs de profondeur à utiliser pour le frambuffer. Ces +valeurs doivent être dans `[0.0f, 1.0f]` mais `minDepth` peut être supérieure à `maxDepth`. Si vous ne faites rien de +particulier contentez-vous des valeurs `0.0f` et `1.0f`. + +Alors que les viewports définissent la transformation de l'image vers le framebuffer, les rectangles de ciseaux +définissent la région de pixels qui sera conservée. Tout pixel en dehors des rectangles de ciseaux seront +éliminés par le rasterizer. Ils fonctionnent plus comme un filtre que comme une transformation. Les différence sont +illustrée ci-dessous. Notez que le rectangle de ciseau dessiné sous l'image de gauche n'est qu'une des possibilités : +tout rectangle plus grand que le viewport aurait fonctionné. + +![](/images/viewports_scissors.png) + +Dans ce tutoriel nous voulons dessiner sur la totalité du framebuffer, et ce sans transformation. Nous +définirons donc un rectangle de ciseaux couvrant tout le frambuffer : + +```c++ +VkRect2D scissor{}; +scissor.offset = {0, 0}; +scissor.extent = swapChainExtent; +``` + +Le viewport et le rectangle de ciseau se combinent en un *viewport state* à l'aide de la structure +`VkPipelineViewportStateCreateInfo`. Il est possible sur certaines cartes graphiques d'utiliser plusieurs viewports +et rectangles de ciseaux, c'est pourquoi la structure permet d'envoyer des tableaux de ces deux données. +L'utilisation de cette possibilité nécessite de l'activer au préalable lors de la création du logical device. + +```c++ +VkPipelineViewportStateCreateInfo viewportState{}; +viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; +viewportState.viewportCount = 1; +viewportState.pViewports = &viewport; +viewportState.scissorCount = 1; +viewportState.pScissors = &scissor; +``` + +## Rasterizer + +Le rasterizer récupère la géométrie définie par des sommets et calcule les fragments qu'elle recouvre. Ils sont ensuite +traités par le fragment shaders. Il réalise également un +[test de profondeur](https://en.wikipedia.org/wiki/Z-buffering), le +[face culling](https://en.wikipedia.org/wiki/Back-face_culling) et le test de ciseau pour vérifier si le fragment doit +effectivement être traité ou non. Il peut être configuré pour émettre des fragments remplissant tous les polygones ou +bien ne remplissant que les cotés (wireframe rendering). Tout cela se configure dans la structure +`VkPipelineRasterizationStateCreateInfo`. + +```c++ +VkPipelineRasterizationStateCreateInfo rasterizer{}; +rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; +rasterizer.depthClampEnable = VK_FALSE; +``` + +Si le membre `depthClampEnable` est mis à `VK_TRUE`, les fragments au-delà des plans near et far ne pas supprimés +mais affichés à cette distance. Cela est utile dans quelques situations telles que les shadow maps. Cela aussi doit +être explicitement activé lors de la mise en place du logical device. + +```c++ +rasterizer.rasterizerDiscardEnable = VK_FALSE; +``` + +Si le membre `rasterizerDiscardEnable` est mis à `VK_TRUE`, aucune géométrie ne passe l'étape du rasterizer, ce qui +désactive purement et simplement toute émission de donnée vers le frambuffer. + +```c++ +rasterizer.polygonMode = VK_POLYGON_MODE_FILL; +``` + +Le membre `polygonMode` définit la génération des fragments pour la géométrie. Les modes suivants sont disponibles : + +* `VK_POLYGON_MODE_FILL` : remplit les polygones de fragments +* `VK_POLYGON_MODE_LINE` : les côtés des polygones sont dessinés comme des lignes +* `VK_POLYGON_MODE_POINT` : les sommets sont dessinées comme des points + +Tout autre mode que fill doit être activé lors de la mise en place du logical device. + +```c++ +rasterizer.lineWidth = 1.0f; +``` + +Le membre `lineWidth` définit la largeur des lignes en terme de fragments. La taille maximale supportée dépend du GPU +et pour toute valeur autre que `1.0f` l'extension `wideLines` doit être activée. + +```c++ +rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; +rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; +``` + +Le membre `cullMode` détermine quel type de face culling utiliser. Vous pouvez désactiver tout ce filtrage, +n'éliminer que les faces de devant, que celles de derrière ou éliminer toutes les faces. Le membre `frontFace` +indique l'ordre d'évaluation des vertices pour dire que la face est devant ou derrière, qui est le sens des +aiguilles d'une montre ou le contraire. + +```c++ +rasterizer.depthBiasEnable = VK_FALSE; +rasterizer.depthBiasConstantFactor = 0.0f; // Optionnel +rasterizer.depthBiasClamp = 0.0f; // Optionnel +rasterizer.depthBiasSlopeFactor = 0.0f; // Optionnel +``` + +Le rasterizer peut altérer la profondeur en y ajoutant une valeur constante ou en la modifiant selon l'inclinaison du +fragment. Ces possibilités sont parfois exploitées pour le shadow mapping mais nous ne les utiliserons pas. Laissez +`depthBiasEnabled` à la valeur `VK_FALSE`. + +## Multisampling + +La structure `VkPipelineMultisampleCreateInfo` configure le multisampling, l'un des outils permettant de réaliser +[l'anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing). Le multisampling combine les résultats +d'invocations du fragment shader sur des fragments de différents polygones qui résultent au même pixel. Cette +superposition arrive plutôt sur les limites entre les géométries, et c'est aussi là que les problèmes visuels de +hachage arrivent le plus. Dans la mesure où le fragment shader n'a pas besoin d'être invoqué plusieurs fois si seul un +polygone correspond à un pixel, cette approche est beaucoup plus efficace que d'augmenter la résolution de la texture. +Son utilisation nécessite son activation au niveau du GPU. + +```c++ +VkPipelineMultisampleStateCreateInfo multisampling{}; +multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; +multisampling.sampleShadingEnable = VK_FALSE; +multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; +multisampling.minSampleShading = 1.0f; // Optionnel +multisampling.pSampleMask = nullptr; // Optionnel +multisampling.alphaToCoverageEnable = VK_FALSE; // Optionnel +multisampling.alphaToOneEnable = VK_FALSE; // Optionnel +``` + +Nous reverrons le multisampling plus tard, pour l'instant laissez-le désactivé. + +## Tests de profondeur et de pochoir + +Si vous utilisez un buffer de profondeur (depth buffer) et/ou de pochoir (stencil buffer) vous devez configurer les +tests de profondeur et de pochoir avec la structure `VkPipelineDepthStencilStateCreateInfo`. Nous n'avons aucun de +ces buffers donc nous indiquerons `nullptr` à la place d'une structure. Nous y reviendrons au chapitre sur le depth +buffering. + +## Color blending + +La couleur donnée par un fragment shader doit être combinée avec la couleur déjà présente dans le framebuffer. Cette +opération s'appelle color blending et il y a deux manières de la réaliser : + +* Mélanger linéairement l'ancienne et la nouvelle couleur pour créer la couleur finale +* Combiner l'ancienne et la nouvelle couleur à l'aide d'une opération bit à bit + +Il y a deux types de structures pour configurer le color blending. La première, +`VkPipelineColorBlendAttachmentState`, contient une configuration pour chaque framebuffer et la seconde, +`VkPipelineColorBlendStateCreateInfo` contient les paramètres globaux pour ce color blending. Dans notre cas nous +n'avons qu'un seul framebuffer : + +```c++ +VkPipelineColorBlendAttachmentState colorBlendAttachment{}; +colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; +colorBlendAttachment.blendEnable = VK_FALSE; +colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optionnel +colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optionnel +colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optionnel +colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optionnel +colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optionnel +colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optionnel +``` + +Cette structure spécifique de chaque framebuffer vous permet de configurer le color blending. L'opération sera +effectuée à peu près comme ce pseudocode le montre : + +```c++ +if (blendEnable) { + finalColor.rgb = (srcColorBlendFactor * newColor.rgb) (dstColorBlendFactor * oldColor.rgb); + finalColor.a = (srcAlphaBlendFactor * newColor.a) (dstAlphaBlendFactor * oldColor.a); +} else { + finalColor = newColor; +} + +finalColor = finalColor & colorWriteMask; +``` + +Si `blendEnable` vaut `VK_FALSE` la nouvelle couleur du fragment shader est inscrite dans le framebuffer sans +modification et sans considération de la valeur déjà présente dans le framebuffer. Sinon les deux opérations de +mélange sont exécutées pour former une nouvelle couleur. Un AND binaire lui est appliquée avec `colorWriteMask` pour +déterminer les canaux devant passer. + +L'utilisation la plus commune du mélange de couleurs utilise le canal alpha pour déterminer l'opacité du matériau et +donc le mélange lui-même. La couleur finale devrait alors être calculée ainsi : + +```c++ +finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor; +finalColor.a = newAlpha.a; +``` + +Avec cette méthode la valeur alpha correspond à une pondération pour la nouvelle valeur par rapport à l'ancienne. Les +paramètres suivants permettent de faire exécuter ce calcul : + +```c++ +colorBlendAttachment.blendEnable = VK_TRUE; +colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; +colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; +colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; +colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; +colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; +colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; +``` + +Vous pouvez trouver toutes les opérations possibles dans les énumérations `VkBlendFactor` et `VkBlendOp` dans la +spécification. + +La seconde structure doit posséder une référence aux structures spécifiques des framebuffers. Vous pouvez également y +indiquer des constantes utilisables lors des opérations de mélange que nous venons de voir. + +```c++ +VkPipelineColorBlendStateCreateInfo colorBlending{}; +colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; +colorBlending.logicOpEnable = VK_FALSE; +colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optionnel +colorBlending.attachmentCount = 1; +colorBlending.pAttachments = &colorBlendAttachment; +colorBlending.blendConstants[0] = 0.0f; // Optionnel +colorBlending.blendConstants[1] = 0.0f; // Optionnel +colorBlending.blendConstants[2] = 0.0f; // Optionnel +colorBlending.blendConstants[3] = 0.0f; // Optionnel +``` + +Si vous voulez utiliser la seconde méthode de mélange (la combinaison bit à bit) vous devez indiquer `VK_TRUE` au +membre `logicOpEnable` et déterminer l'opération dans `logicOp`. Activer ce mode de mélange désactive automatiquement +la première méthode aussi radicalement que si vous aviez indiqué `VK_FALSE` au membre `blendEnable` de la +précédente structure pour chaque framebuffer. Le membre `colorWriteMask` sera également utilisé dans ce second mode pour +déterminer les canaux affectés. Il est aussi possible de désactiver les deux modes comme nous l'avons fait ici. Dans +ce cas les résultats des invocations du fragment shader seront écrits directement dans le framebuffer. + +## États dynamiques + +Un petit nombre d'états que nous avons spécifiés dans les structures précédentes peuvent en fait être altérés +sans avoir à recréer la pipeline. On y trouve la taille du viewport, la largeur des lignes et les constantes de mélange. +Pour cela vous devrez remplir la structure `VkPipelineDynamicStateCreateInfo` comme suit : + +```c++ +std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_LINE_WIDTH +}; + +VkPipelineDynamicStateCreateInfo dynamicState{}; +dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; +dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); +dynamicState.pDynamicStates = dynamicStates.data(); +``` + +Les valeurs données lors de la configuration seront ignorées et vous devrez en fournir au moment du rendu. Nous y +reviendrons plus tard. Cette structure peut être remplacée par `nullptr` si vous ne voulez pas utiliser de dynamisme +sur ces états. + +## Pipeline layout + +Les variables `uniform` dans les shaders sont des données globales similaires aux états dynamiques. Elles doivent +être déterminées lors du rendu pour altérer les calculs des shaders sans avoir à les recréer. Elles sont très utilisées +pour fournir les matrices de transformation au vertex shader et pour créer des samplers de texture dans les fragment +shaders. + +Ces variables doivent être configurées lors de la création de la pipeline en créant une variable +de type `VkPipelineLayout`. Même si nous n'en utilisons pas dans nos shaders actuels nous devons en créer un vide. + +Créez un membre donnée pour stocker la structure car nous en aurons besoin plus tard. + +```c++ +VkPipelineLayout pipelineLayout; +``` + +Créons maintenant l'objet dans la fonction `createGraphicsPipline` : + +```c++ +VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; +pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +pipelineLayoutInfo.setLayoutCount = 0; // Optionnel +pipelineLayoutInfo.pSetLayouts = nullptr; // Optionnel +pipelineLayoutInfo.pushConstantRangeCount = 0; // Optionnel +pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optionnel + +if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("échec de la création du pipeline layout!"); +} +``` + +Cette structure informe également sur les _push constants_, une autre manière de passer des valeurs dynamiques au +shaders que nous verrons dans un futur chapitre. Le pipeline layout sera utilisé pendant toute la durée du +programme, nous devons donc le détruire dans la fonction `cleanup` : + +```c++ +void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + ... +} +``` + +## Conclusion + +Voila tout ce qu'il y a à savoir sur les étapes à fonction fixée! Leur configuration représente un gros travail +mais nous sommes au courant de tout ce qui se passe dans la pipeline graphique, ce qui réduit les chances de +comportement imprévu à cause d'un paramètre par défaut oublié. + +Il reste cependant encore un objet à créer avant du finaliser la pipeline graphique. Cet objet s'appelle +[passe de rendu](!fr/Dessiner_un_triangle/Pipeline_graphique_basique/Render_pass). + +[Code C++](/code/10_fixed_functions.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/03_Render_pass.md b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/03_Render_pass.md new file mode 100644 index 00000000..37270662 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/03_Render_pass.md @@ -0,0 +1,191 @@ +## Préparation + +Avant de finaliser la création de la pipeline nous devons informer Vulkan des attachements des framebuffers utilisés +lors du rendu. Nous devons indiquer combien chaque framebuffer aura de buffers de couleur et de profondeur, combien de +samples il faudra utiliser avec chaque frambuffer et comment les utiliser tout au long des opérations de rendu. Toutes +ces informations sont contenues dans un objet appelé *render pass*. Pour le configurer, créons la fonction +`createRenderPass`. Appelez cette fonction depuis `initVulkan` avant `createGraphicsPipeline`. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); +} + +... + +void createRenderPass() { + +} +``` + +## Description de l'attachement + +Dans notre cas nous aurons un seul attachement de couleur, et c'est une image de la swap chain. + +```c++ +void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; +} +``` + +Le `format` de l'attachement de couleur est le même que le format de l'image de la swap chain. Nous n'utilisons pas +de multisampling pour le moment donc nous devons indiquer que nous n'utilisons qu'un seul sample. + +```c++ +colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; +colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; +``` + +Les membres `loadOp` et `storeOp` définissent ce qui doit être fait avec les données de l'attachement respectivement +avant et après le rendu. Pour `loadOp` nous avons les choix suivants : + +* `VK_ATTACHMENT_LOAD_OP_LOAD` : conserve les données présentes dans l'attachement +* `VK_ATTACHMENT_LOAD_OP_CLEAR` : remplace le contenu par une constante +* `VK_ATTACHMENT_LOAD_OP_DONT_CARE` : ce qui existe n'est pas défini et ne nous intéresse pas + +Dans notre cas nous utiliserons l'opération de remplacement pour obtenir un framebuffer noir avant d'afficher une +nouvelle image. Il n'y a que deux possibilités pour le membre `storeOp` : + +* `VK_ATTACHMENT_STORE_OP_STORE` : le rendu est gardé en mémoire et accessible plus tard +* `VK_ATTACHMENT_STORE_OP_DONT_CARE` : le contenu du framebuffer est indéfini dès la fin du rendu + +Nous voulons voir le triangle à l'écran donc nous voulons l'opération de stockage. + +```c++ +colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; +colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; +``` + +Les membres `loadOp` et `storeOp` s'appliquent aux données de couleur et de profondeur, et `stencilLoadOp` et +`stencilStoreOp` s'appliquent aux données de stencil. Notre application n'utilisant pas de stencil buffer, nous +pouvons indiquer que les données ne nous intéressent pas. + +```c++ +colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; +``` + +Les textures et les framebuffers dans Vulkan sont représentés par des objets de type `VkImage` possédant un certain +format de pixels. Cependant l'organisation des pixels dans la mémoire peut changer selon ce que vous faites de cette +image. + +Les organisations les plus communes sont : + +* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` : images utilisées comme attachements de couleur +* `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` : images présentées à une swap chain +* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` : image utilisées comme destination d'opérations de copie de mémoire + +Nous discuterons plus précisément de ce sujet dans le chapitre sur les textures. Ce qui compte pour le moment est que +les images doivent changer d'organisation mémoire selon les opérations qui leur sont appliquées au long de l'exécution +de la pipeline. + +Le membre `initialLayout` spécifie l'organisation de l'image avant le début du rendu. Le membre `finalLayout` fournit +l'organisation vers laquelle l'image doit transitionner à la fin du rendu. La valeur `VK_IMAGE_LAYOUT_UNDEFINED` +indique que le format précédent de l'image ne nous intéresse pas, ce qui peut faire perdre les données précédentes. +Mais ce n'est pas un problème puisque nous effaçons de toute façon toutes les données avant le rendu. Puis, afin de +rendre l'image compatible avec la swap chain, nous fournissons `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` pour `finalLayout`. + +## Subpasses et références aux attachements + +Une unique passe de rendu est composée de plusieurs subpasses. Les subpasses sont des opérations de rendu +dépendant du contenu présent dans le framebuffer quand elles commencent. Elles peuvent consister en des opérations de +post-processing exécutées l'une après l'autre. En regroupant toutes ces opérations en une seule passe, Vulkan peut +alors réaliser des optimisations et conserver de la bande passante pour de potentiellement meilleures performances. +Pour notre triangle nous nous contenterons d'une seule subpasse. + +Chacune d'entre elle référence un ou plusieurs attachements décrits par les structures que nous avons vues +précédemment. Ces références sont elles-mêmes des structures du type `VkAttachmentReference` et ressemblent à cela : + +```c++ +VkAttachmentReference colorAttachmentRef{}; +colorAttachmentRef.attachment = 0; +colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; +``` + +Le paramètre `attachment` spécifie l'attachement à référencer à l'aide d'un indice correspondant à la position de la +structure dans le tableau de descriptions d'attachements. Notre tableau ne consistera qu'en une seule référence donc +son indice est nécessairement `0`. Le membre `layout` donne l'organisation que l'attachement devrait avoir au début d'une +subpasse utilsant cette référence. Vulkan changera automatiquement l'organisation de l'attachement quand la subpasse +commence. Nous voulons que l'attachement soit un color buffer, et pour cela la meilleure performance sera obtenue avec +`VK_IMAGE_LAYOUT_COLOR_OPTIMAL`, comme son nom le suggère. + +La subpasse est décrite dans la structure `VkSubpassDescription` : + +```c++ +VkSubpassDescription subpass{}; +subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; +``` + +Vulkan supportera également des *compute subpasses* donc nous devons indiquer que celle que nous créons est destinée +aux graphismes. Nous spécifions ensuite la référence à l'attachement de couleurs : + +```c++ +subpass.colorAttachmentCount = 1; +subpass.pColorAttachments = &colorAttachmentRef; +``` + +L'indice de cet attachement est indiqué dans le fragment shader avec le `location = 0` dans la directive +`layout(location = 0) out vec4 outColor`. + +Les types d'attachements suivants peuvent être indiqués dans une subpasse : + +* `pInputAttachments` : attachements lus depuis un shader +* `pResolveAttachments` : attachements utilisés pour le multisampling d'attachements de couleurs +* `pDepthStencilAttachment` : attachements pour la profondeur et le stencil +* `pPreserveAttachments` : attachements qui ne sont pas utilisés par cette subpasse mais dont les données doivent +être conservées + +## Passe de rendu + +Maintenant que les attachements et une subpasse simple ont été décrits nous pouvons enfin créer la render pass. +Créez une nouvelle variable du type `VkRenderPass` au-dessus de la variable `pipelineLayout` : + +```c++ +VkRenderPass renderPass; +VkPipelineLayout pipelineLayout; +``` + +L'objet représentant la render pass peut alors être créé en remplissant la structure `VkRenderPassCreateInfo` dans +laquelle nous devons remplir un tableau d'attachements et de subpasses. Les objets `VkAttachmentReference` référencent +les attachements en utilisant les indices de ce tableau. + +```c++ +VkRenderPassCreateInfo renderPassInfo{}; +renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; +renderPassInfo.attachmentCount = 1; +renderPassInfo.pAttachments = &colorAttachment; +renderPassInfo.subpassCount = 1; +renderPassInfo.pSubpasses = &subpass; + +if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("échec de la création de la render pass!"); +} +``` + +Comme l'organisation de la pipeline, nous aurons à utiliser la référence à la passe de rendu tout au long du +programme. Nous devons donc la détruire dans la fonction `cleanup` : + +```c++ +void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + ... +} +``` + +Nous avons eu beaucoup de travail, mais nous allons enfin créer la pipeline graphique et l'utiliser dès le prochain +chapitre! + +[Code C++](/code/11_render_passes.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/04_Conclusion.md b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/04_Conclusion.md new file mode 100644 index 00000000..74d530b1 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/04_Conclusion.md @@ -0,0 +1,107 @@ +Nous pouvons maintenant combiner toutes les structures et tous les objets des chapitres précédentes pour créer la +pipeline graphique! Voici un petit récapitulatif des objets que nous avons : + +* Étapes shader : les modules shader définissent le fonctionnement des étapes programmables de la pipeline graphique +* Étapes à fonction fixée : plusieurs structures paramètrent les étapes à fonction fixée comme l'assemblage des +entrées, le rasterizer, le viewport et le mélange des couleurs +* Organisation de la pipeline : les uniformes et push constants utilisées par les shaders, auxquelles on attribue une +valeur pendant l'exécution de la pipeline +* Render pass : les attachements référencés par la pipeline et leurs utilisations + +Tout cela combiné définit le fonctionnement de la pipeline graphique. Nous pouvons maintenant remplir la structure +`VkGraphicsPipelineCreateInfo` à la fin de la fonction `createGraphicsPipeline`, mais avant les appels à la fonction +`vkDestroyShaderModule` pour ne pas invalider les shaders que la pipeline utilisera. + +Commençons par référencer le tableau de `VkPipelineShaderStageCreateInfo`. + +```c++ +VkGraphicsPipelineCreateInfo pipelineInfo{}; +pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; +pipelineInfo.stageCount = 2; +pipelineInfo.pStages = shaderStages; +``` + +Puis donnons toutes les structure décrivant les étapes à fonction fixée. + +```c++ +pipelineInfo.pVertexInputState = &vertexInputInfo; +pipelineInfo.pInputAssemblyState = &inputAssembly; +pipelineInfo.pViewportState = &viewportState; +pipelineInfo.pRasterizationState = &rasterizer; +pipelineInfo.pMultisampleState = &multisampling; +pipelineInfo.pDepthStencilState = nullptr; // Optionnel +pipelineInfo.pColorBlendState = &colorBlending; +pipelineInfo.pDynamicState = nullptr; // Optionnel +``` + +Après cela vient l'organisation de la pipeline, qui est une référence à un objet Vulkan plutôt qu'une structure. + +```c++ +pipelineInfo.layout = pipelineLayout; +``` + +Finalement nous devons fournir les références à la render pass et aux indices des subpasses. Il est aussi possible +d'utiliser d'autres render passes avec cette pipeline mais elles doivent être compatibles avec `renderPass`. La +signification de compatible est donnée +[ici](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility), mais nous +n'utiliserons pas cette possibilité dans ce tutoriel. + +```c++ +pipelineInfo.renderPass = renderPass; +pipelineInfo.subpass = 0; +``` + +Il nous reste en fait deux paramètres : `basePipelineHandle` et `basePipelineIndex`. Vulkan vous permet de créer une +nouvelle pipeline en "héritant" d'une pipeline déjà existante. L'idée derrière cette fonctionnalité est qu'il +est moins coûteux de créer une pipeline à partir d'une qui existe déjà, mais surtout que passer d'une pipeline à une +autre est plus rapide si elles ont un même parent. Vous pouvez spécifier une pipeline de deux manières : soit en +fournissant une référence soit en donnant l'indice de la pipeline à hériter. Nous n'utilisons pas cela donc +nous indiquerons une référence nulle et un indice invalide. Ces valeurs ne sont de toute façon utilisées que si le champ +`flags` de la structure `VkGraphicsPipelineCreateInfo` comporte `VK_PIPELINE_CREATE_DERIVATIVE_BIT`. + +```c++ +pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optionnel +pipelineInfo.basePipelineIndex = -1; // Optionnel +``` + +Préparons-nous pour l'étape finale en créant un membre donnée où stocker la référence à la `VkPipeline` : + +```c++ +VkPipeline graphicsPipeline; +``` + +Et créons enfin la pipeline graphique : + +```c++ +if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("échec de la création de la pipeline graphique!"); +} +``` + +La fonction `vkCreateGraphicsPipelines` possède en fait plus de paramètres que les fonctions de création d'objet que +nous avons pu voir jusqu'à présent. Elle peut en effet accepter plusieurs structures `VkGraphicsPipelineCreateInfo` +et créer plusieurs `VkPipeline` en un seul appel. + +Le second paramètre que nous n'utilisons pas ici (mais que nous reverrons dans un chapitre qui lui sera dédié) sert à +fournir un objet `VkPipelineCache` optionnel. Un tel objet peut être stocké et réutilisé entre plusieurs appels de la +fonction et même entre plusieurs exécutions du programme si son contenu est correctement stocké dans un fichier. Cela +permet de grandement accélérer la création des pipelines. + +La pipeline graphique est nécessaire à toutes les opérations d'affichage, nous ne devrons donc la supprimer qu'à la fin +du programme dans la fonction `cleanup` : + +```c++ +void cleanup() { + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + ... +} +``` + +Exécutez votre programme pour vérifier que tout ce travail a enfin résulté dans la création d'une pipeline graphique. +Nous sommes de plus en plus proches d'avoir un dessin à l'écran! Dans les prochains chapitres nous générerons les +framebuffers à partir des images de la swap chain et préparerons les commandes d'affichage. + +[Code C++](/code/12_graphics_pipeline_complete.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/00_Framebuffers.md b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/00_Framebuffers.md new file mode 100644 index 00000000..76780671 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/00_Framebuffers.md @@ -0,0 +1,100 @@ +Nous avons beaucoup parlé de framebuffers dans les chapitres précédents, et nous avons mis en place la render pass +pour qu'elle en accepte un du même format que les images de la swap chain. Pourtant nous n'en avons encore créé aucun. + +Les attachements de différents types spécifiés durant la render pass sont liés en les considérant dans des objets de +type `VkFramebuffer`. Un tel objet référence toutes les `VkImageView` utilisées comme attachements par une passe. +Dans notre cas nous n'en aurons qu'un : un attachement de couleur, qui servira de cible d'affichage uniquement. +Cependant l'image utilisée dépendra de l'image fournie par la swap chain lors de la requête pour l'affichage. Nous +devons donc créer un framebuffer pour chacune des images de la swap chain et utiliser le bon au moment de l'affichage. + +Pour cela créez un autre `std::vector` qui contiendra des framebuffers : + +```c++ +std::vector swapChainFramebuffers; +``` + +Nous allons remplir ce `vector` depuis une nouvelle fonction `createFramebuffers` que nous appellerons depuis +`initVulkan` juste après la création de la pipeline graphique : + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); +} + +... + +void createFramebuffers() { + +} +``` + +Commencez par redimensionner le conteneur afin qu'il puisse stocker tous les framebuffers : + +```c++ +void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); +} +``` + +Nous allons maintenant itérer à travers toutes les images et créer un framebuffer à partir de chacune d'entre elles : + +```c++ +for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("échec de la création d'un framebuffer!"); + } +} +``` + +Comme vous le pouvez le voir la création d'un framebuffer est assez simple. Nous devons d'abord indiquer avec quelle +`renderPass` le framebuffer doit être compatible. Sachez que si vous voulez utiliser un framebuffer avec plusieurs +render passes, les render passes spécifiées doivent être compatibles entre elles. La compatibilité signifie ici +approximativement qu'elles utilisent le même nombre d'attachements du même type. Ceci implique qu'il ne faut pas +s'attendre à ce qu'une render pass puisse ignorer certains attachements d'un framebuffer qui en aurait trop. + +Les paramètres `attachementCount` et `pAttachments` doivent donner la taille du tableau contenant les `VkImageViews` +qui servent d'attachements. + +Les paramètres `width` et `height` sont évidents. Le membre `layers` correspond au nombres de couches dans les images +fournies comme attachements. Les images de la swap chain n'ont toujours qu'une seule couche donc nous indiquons `1`. + +Nous devons détruire les framebuffers avant les image views et la render pass dans la fonction `cleanup` : + +```c++ +void cleanup() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + ... +} +``` + +Nous avons atteint le moment où tous les objets sont prêts pour l'affichage. Dans le prochain chapitre nous allons +écrire les commandes d'affichage. + +[Code C++](/code/13_framebuffers.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/01_Command_buffers.md b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/01_Command_buffers.md new file mode 100644 index 00000000..5d9206c5 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/01_Command_buffers.md @@ -0,0 +1,284 @@ +Les commandes Vulkan, comme les opérations d'affichage et de transfert mémoire, ne sont pas réalisées avec des appels de +fonctions. Il faut pré-enregistrer toutes les opérations dans des _command buffers_. L'avantage est que vous pouvez +préparer tout ce travail à l'avance et depuis plusieurs threads, puis vous contenter d'indiquer à Vulkan quel command +buffer doit être exécuté. Cela réduit considérablement la bande passante entre le CPU et le GPU et améliore grandement +les performances. + +## Command pools + +Nous devons créer une *command pool* avant de pouvoir créer les command buffers. Les command pools gèrent la mémoire +utilisée par les buffers, et c'est de fait les command pools qui nous instancient les command buffers. Ajoutez un +nouveau membre donnée à la classe de type `VkCommandPool` : + +```c++ +VkCommandPool commandPool; +``` + +Créez ensuite la fonction `createCommandPool` et appelez-la depuis `initVulkan` après la création du framebuffer. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); +} + +... + +void createCommandPool() { + +} +``` + +La création d'une command pool ne nécessite que deux paramètres : + +```c++ +QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + +VkCommandPoolCreateInfo poolInfo{}; +poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; +poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); +poolInfo.flags = 0; // Optionel +``` + +Les commands buffers sont exécutés depuis une queue, comme la queue des graphismes et de présentation que nous avons +récupérées. Une command pool ne peut allouer des command buffers compatibles qu'avec une seule famille de queues. Nous +allons enregistrer des commandes d'affichage, c'est pourquoi nous avons récupéré une queue de graphismes. + +Il existe deux valeurs acceptées par `flags` pour les command pools : + +* `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` : informe que les command buffers sont ré-enregistrés très souvent, ce qui +peut inciter Vulkan (et donc le driver) à ne pas utiliser le même type d'allocation +* `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT` : permet aux command buffers d'être ré-enregistrés individuellement, +ce que les autres configurations ne permettent pas + +Nous n'enregistrerons les command buffers qu'une seule fois au début du programme, nous n'aurons donc pas besoin de ces +fonctionnalités. + +```c++ +if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("échec de la création d'une command pool!"); +} +``` + +Terminez la création de la command pool à l'aide de la fonction `vkCreateComandPool`. Elle ne comprend pas de +paramètre particulier. Les commandes seront utilisées tout au long du programme pour tout affichage, nous ne devons +donc la détruire que dans la fonction `cleanup` : + +```c++ +void cleanup() { + vkDestroyCommandPool(device, commandPool, nullptr); + + ... +} +``` + +## Allocation des command buffers + +Nous pouvons maintenant allouer des command buffers et enregistrer les commandes d'affichage. Dans la mesure où l'une +des commandes consiste à lier un framebuffer nous devrons les enregistrer pour chacune des images de la swap chain. +Créez pour cela une liste de `VkCommandBuffer` et stockez-la dans un membre donnée de la classe. Les command buffers +sont libérés avec la destruction de leur command pool, nous n'avons donc pas à faire ce travail. + +```c++ +std::vector commandBuffers; +``` + +Commençons maintenant à travailler sur notre fonction `createCommandBuffers` qui allouera et enregistrera les command +buffers pour chacune des images de la swap chain. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffers(); +} + +... + +void createCommandBuffers() { + commandBuffers.resize(swapChainFramebuffers.size()); +} +``` + +Les command buffers sont alloués par la fonction `vkAllocateCommandBuffers` qui prend en paramètre une structure du +type `VkCommandBufferAllocateInfo`. Cette structure spécifie la command pool et le nombre de buffers à allouer depuis +celle-ci : + +```c++ +VkCommandBufferAllocateInfo allocInfo{}; +allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; +allocInfo.commandPool = commandPool; +allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; +allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + +if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("échec de l'allocation de command buffers!"); +} +``` + +Les command buffers peuvent être *primaires* ou *secondaires*, ce que l'on indique avec le paramètre `level`. Il peut +prendre les valeurs suivantes : + +* `VK_COMMAND_BUFFER_LEVEL_PRIMARY` : peut être envoyé à une queue pour y être exécuté mais ne peut être appelé par +d'autres command buffers +* `VK_COMMAND_BUFFER_LEVEL_SECONDARY` : ne peut pas être directement émis à une queue mais peut être appelé par un autre +command buffer + +Nous n'utiliserons pas la fonctionnalité de command buffer secondaire ici. Sachez que le mécanisme de command buffer +secondaire est à la base de la génération rapie de commandes d'affichage depuis plusieurs threads. + +## Début de l'enregistrement des commandes + +Nous commençons l'enregistrement des commandes en appelant `vkBeginCommandBuffer`. Cette fonction prend une petite +structure du type `VkCommandBufferBeginInfo` en argument, permettant d'indiquer quelques détails sur l'utilisation du +command buffer. + +```c++ +for (size_t i = 0; i < commandBuffers.size(); i++) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optionnel + beginInfo.pInheritanceInfo = nullptr; // Optionel + + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("erreur au début de l'enregistrement d'un command buffer!"); + } +} +``` + +L'utilisation du command buffer s'indique avec le paramètre `flags`, qui peut prendre les valeurs suivantes : + +* `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT` : le command buffer sera ré-enregistré après son utilisation, donc +invalidé une fois son exécution terminée +* `VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT` : ce command buffer secondaire sera intégralement exécuté dans une +unique render pass +* `VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT` : le command buffer peut être ré-envoyé à la queue alors qu'il y est +déjà et/ou est en cours d'exécution + +Nous n'avons pas besoin de ces flags ici. + +Le paramètre `pInheritanceInfo` n'a de sens que pour les command buffers secondaires. +Il indique l'état à hériter de l'appel par le command buffer primaire. + +Si un command buffer est déjà prêt un appel à `vkBeginCommandBuffer` le regénèrera implicitement. Il n'est pas possible +d'enregistrer un command buffer en plusieurs fois. + +## Commencer une render pass + +L'affichage commence par le lancement de la render pass réalisé par `vkCmdBeginRenderPass`. La passe est configurée +à l'aide des paramètres remplis dans une structure de type `VkRenderPassBeginInfo`. + +```c++ +VkRenderPassBeginInfo renderPassInfo{}; +renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; +renderPassInfo.renderPass = renderPass; +renderPassInfo.framebuffer = swapChainFramebuffers[i]; +``` + +Ces premiers paramètres sont la render pass elle-même et les attachements à lui fournir. Nous avons créé un +framebuffer pour chacune des images de la swap chain qui spécifient ces images comme attachements de couleur. + +```c++ +renderPassInfo.renderArea.offset = {0, 0}; +renderPassInfo.renderArea.extent = swapChainExtent; +``` + +Les deux paramètres qui suivent définissent la taille de la zone de rendu. Cette zone de rendu définit où les +chargements et stockages shaders se produiront. Les pixels hors de cette région auront une valeur non définie. Elle +doit correspondre à la taille des attachements pour avoir une performance optimale. + +```c++ +VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; +renderPassInfo.clearValueCount = 1; +renderPassInfo.pClearValues = &clearColor; +``` + +Les deux derniers paramètres définissent les valeurs à utiliser pour remplacer le contenu (fonctionnalité que nous +avions activée avec `VK_ATTACHMENT_LOAD_CLEAR`). J'ai utilisé un noir complètement opaque. + +```c++ +vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); +``` + +La render pass peut maintenant commencer. Toutes les fonctions enregistrables se reconnaisent à leur préfixe `vkCmd`. +Comme elles retournent toutes `void` nous n'avons aucun moyen de gérer d'éventuelles erreurs avant d'avoir fini +l'enregistrement. + +Le premier paramètre de chaque commande est toujours le command buffer qui stockera l'appel. Le second paramètre donne +des détails sur la render pass à l'aide de la structure que nous avons préparée. Le dernier paramètre informe sur la +provenance des commandes pendant l'exécution de la passe. Il peut prendre ces valeurs : + +* `VK_SUBPASS_CONTENTS_INLINE` : les commandes de la render pass seront inclues directement dans le command buffer +(qui est donc primaire) +* `VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFER` : les commandes de la render pass seront fournies par un ou +plusieurs command buffers secondaires + +Nous n'utiliserons pas de command buffer secondaire, nous devons donc fournir la première valeur à la fonction. + +## Commandes d'affichage basiques + +Nous pouvons maintenant activer la pipeline graphique : + +```c++ +vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); +``` + +Le second paramètre indique que la pipeline est bien une pipeline graphique et non de calcul. Nous avons fourni à Vulkan +les opérations à exécuter avec la pipeline graphique et les attachements que le fragment shader devra utiliser. Il ne +nous reste donc plus qu'à lui dire d'afficher un triangle : + +```c++ +vkCmdDraw(commandBuffers[i], 3, 1, 0, 0); +``` + +Le fonction `vkCmdDraw` est assez ridicule quand on sait tout ce qu'elle implique, mais sa simplicité est due +à ce que tout a déjà été préparé en vue de ce moment tant attendu. Elle possède les paramètres suivants en plus du +command buffer concerné : + +* `vertexCount` : même si nous n'avons pas de vertex buffer, nous avons techniquement trois vertices à dessiner +* `instanceCount` : sert au rendu instancié (instanced rendering); indiquez `1` si vous ne l'utilisez pas +* `firstVertex` : utilisé comme décalage dans le vertex buffer et définit ainsi la valeur la plus basse pour +`glVertexIndex` +* `firstInstance` : utilisé comme décalage pour l'instanced rendering et définit ainsi la valeur la plus basse pour +`gl_InstanceIndex` + +## Finitions + +La render pass peut ensuite être terminée : + +```c++ +vkCmdEndRenderPass(commandBuffers[i]); +``` + +Et nous avons fini l'enregistrement du command buffer : + +```c++ +if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("échec de l'enregistrement d'un command buffer!"); +} +``` + +Dans le prochain chapitre nous écrirons le code pour la boucle principale. Elle récupérera une image de la swap chain, +exécutera le bon command buffer et retournera l'image complète à la swap chain. + +[Code C++](/code/14_command_buffers.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git "a/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/02_Rendu_et_pr\303\251sentation.md" "b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/02_Rendu_et_pr\303\251sentation.md" new file mode 100644 index 00000000..957236aa --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/02_Rendu_et_pr\303\251sentation.md" @@ -0,0 +1,626 @@ +## Mise en place + +Nous en sommes au chapitre où tout s'assemble. Nous allons écrire une fonction `drawFrame` qui sera appelée depuis la +boucle principale et affichera les triangles à l'écran. Créez la fonction et appelez-la depuis `mainLoop` : + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } +} + +... + +void drawFrame() { + +} +``` + +## Synchronisation + +Le fonction `drawFrame` réalisera les opérations suivantes : + +* Acquérir une image depuis la swap chain +* Exécuter le command buffer correspondant au framebuffer dont l'attachement est l'image obtenue +* Retourner l'image à la swap chain pour présentation + +Chacune de ces actions n'est réalisée qu'avec un appel de fonction. Cependant ce n'est pas aussi simple : les +opérations sont par défaut exécutées de manière asynchrones. La fonction retourne aussitôt que les opérations sont +lancées, et par conséquent l'ordre d'exécution est indéfini. Cela nous pose problème car chacune des opérations que nous +voulons lancer dépendent des résultats de l'opération la précédant. + +Il y a deux manières de synchroniser les évènements de la swap chain : les *fences* et les *sémaphores*. Ces deux objets +permettent d'attendre qu'une opération se termine en relayant un signal émis par un processus généré par la fonction à +l'origine du lancement de l'opération. + +Ils ont cependant une différence : l'état d'une fence peut être accédé depuis le programme à l'aide de fonctions telles +que `vkWaitForFences` alors que les sémaphores ne le permettent pas. Les fences sont généralement utilisées pour +synchroniser votre programme avec les opérations alors que les sémaphores synchronisent les opérations entre elles. Nous +voulons synchroniser les queues, les commandes d'affichage et la présentation, donc les sémaphores nous conviennent le +mieux. + +## Sémaphores + +Nous aurons besoin d'un premier sémaphore pour indiquer que l'acquisition de l'image s'est bien réalisée, puis d'un +second pour prévenir de la fin du rendu et permettre à l'image d'être retournée dans la swap chain. Créez deux membres +données pour stocker ces sémaphores : + +```c++ +VkSemaphore imageAvailableSemaphore; +VkSemaphore renderFinishedSemaphore; +``` + +Pour leur création nous allons avoir besoin d'une dernière fonction `create...` pour cette partie du tutoriel. +Appelez-la `createSemaphores` : + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffers(); + createSemaphores(); +} + +... + +void createSemaphores() { + +} +``` + +La création d'un sémaphore passe par le remplissage d'une structure de type `VkSemaphoreCreateInfo`. Cependant cette +structure ne requiert pour l'instant rien d'autre que le membre `sType` : + +```c++ +void createSemaphores() { + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; +} +``` + +De futures version de Vulkan ou des extensions pourront à terme donner un intérêt aux membre `flags` et `pNext`, comme +pour d'autres structures. Créez les sémaphores comme suit : + +```c++ +if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) { + + throw std::runtime_error("échec de la création des sémaphores!"); +} +``` + +Les sémaphores doivent être détruits à la fin du programme depuis la fonction `cleanup` : + +```c++ +void cleanup() { + vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); + vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); +``` + +## Acquérir une image de la swap chain + +La première opération à réaliser dans `drawFrame` est d'acquérir une image depuis la swap chain. La swap chain étant une +extension nous allons encore devoir utiliser des fonction suffixées de `KHR` : + +```c++ +void drawFrame() { + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); +} +``` + +Les deux premiers paramètres de `vkAcquireNextImageKHR` sont le logical device et la swap chain depuis laquelle +récupérer les images. Le troisième paramètre spécifie une durée maximale en nanosecondes avant d'abandonner l'attente +si aucune image n'est disponible. Utiliser la plus grande valeur possible pour un `uint32_t` le désactive. + +Les deux paramètres suivants sont les objets de synchronisation qui doivent être informés de la complétion de +l'opération de récupération. Ce sera à partir du moment où le sémaphore que nous lui fournissons reçoit un signal que +nous pouvons commencer à dessiner. + +Le dernier paramètre permet de fournir à la fonction une variable dans laquelle elle stockera l'indice de l'image +récupérée dans la liste des images de la swap chain. Cet indice correspond à la `VkImage` dans notre `vector` +`swapChainImages`. Nous utiliserons cet indice pour invoquer le bon command buffer. + +## Envoi du command buffer + +L'envoi à la queue et la synchronisation de celle-ci sont configurés à l'aide de paramètres dans la structure +`VkSubmitInfo` que nous allons remplir. + +```c++ +VkSubmitInfo submitInfo{}; +submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + +VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; +VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; +submitInfo.waitSemaphoreCount = 1; +submitInfo.pWaitSemaphores = waitSemaphores; +submitInfo.pWaitDstStageMask = waitStages; +``` + +Les trois premiers paramètres (sans compter `sType`) fournissent le sémaphore indiquant si l'opération doit attendre et +l'étape du rendu à laquelle s'arrêter. Nous voulons attendre juste avant l'écriture des couleurs sur l'image. Par contre +nous laissons à l'implémentation la possibilité d'exécuter toutes les étapes précédentes d'ici là. Notez que chaque +étape indiquée dans `waitStages` correspond au sémaphore de même indice fourni dans `waitSemaphores`. + +```c++ +submitInfo.commandBufferCount = 1; +submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; +``` + +Les deux paramètres qui suivent indiquent les command buffers à exécuter. Nous devons ici fournir le command buffer +qui utilise l'image de la swap chain que nous venons de récupérer comme attachement de couleur. + +```c++ +VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; +submitInfo.signalSemaphoreCount = 1; +submitInfo.pSignalSemaphores = signalSemaphores; +``` + +Les paramètres `signalSemaphoreCount` et `pSignalSemaphores` indiquent les sémaphores auxquels indiquer que les command +buffers ont terminé leur exécution. Dans notre cas nous utiliserons notre `renderFinishedSemaphore`. + +```c++ +if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("échec de l'envoi d'un command buffer!"); +} +``` + +Nous pouvons maintenant envoyer notre command buffer à la queue des graphismes en utilisant `vkQueueSubmit`. Cette +fonction prend en argument un tableau de structures de type `VkSubmitInfo` pour une question d'efficacité. Le dernier +paramètre permet de fournir une fence optionnelle. Celle-ci sera prévenue de la fin de l'exécution des command +buffers. Nous n'en utilisons pas donc passerons `VK_NULL_HANDLE`. + +## Subpass dependencies + +Les subpasses s'occupent automatiquement de la transition de l'organisation des images. Ces transitions sont contrôlées +par des *subpass dependencies*. Elles indiquent la mémoire et l'exécution entre les subpasses. Nous n'avons certes +qu'une seule subpasse pour le moment, mais les opérations avant et après cette subpasse comptent aussi comme des +subpasses implicites. + +Il existe deux dépendances préexistantes capables de gérer les transitions au début et à la fin de la render pass. Le +problème est que cette première dépendance ne s'exécute pas au bon moment. Elle part du principe que la transition de +l'organisation de l'image doit être réalisée au début de la pipeline, mais dans notre programme l'image n'est pas encore +acquise à ce moment! Il existe deux manières de régler ce problème. Nous pourrions changer `waitStages` pour +`imageAvailableSemaphore` à `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` pour être sûrs que la pipeline ne commence pas avant +que l'image ne soit acquise, mais nous perdrions en performance car les shaders travaillant sur les vertices n'ont pas +besoin de l'image. Il faudrait faire quelque chose de plus subtil. Nous allons donc plutôt faire attendre la render +pass à l'étape `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` et faire la transition à ce moment. Cela nous donne de +plus une bonne excuse pour s'intéresser au fonctionnement des subpass dependencies. + +Celles-ci sont décrites dans une structure de type `VkSubpassDependency`. Créez en une dans la fonction +`createRenderPass` : + +```c++ +VkSubpassDependency dependency{}; +dependency.srcSubpass = VK_SUBPASS_EXTERNAL; +dependency.dstSubpass = 0; +``` + +Les deux premiers champs permettent de fournir l'indice de la subpasse d'origine et de la subpasse d'arrivée. La valeur +particulière `VK_SUBPASS_EXTERNAL` réfère à la subpass implicite soit avant soit après la render pass, selon que +cette valeur est indiquée dans respectivement `srcSubpass` ou `dstSubpass`. L'indice `0` correspond à notre +seule et unique subpasse. La valeur fournie à `dstSubpass` doit toujours être supérieure à `srcSubpass` car sinon une +boucle infinie peut apparaître (sauf si une des subpasse est `VK_SUBPASS_EXTERNAL`). + +```c++ +dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +dependency.srcAccessMask = 0; +``` + +Les deux paramètres suivants indiquent les opérations à attendre et les étapes durant lesquelles les opérations à +attendre doivent être considérées. Nous voulons attendre la fin de l'extraction de l'image avant d'y accéder, hors +ceci est déjà configuré pour être synchronisé avec l'étape d'écriture sur l'attachement. C'est pourquoi nous n'avons +qu'à attendre à cette étape. + +```c++ +dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; +``` + +Nous indiquons ici que les opérations qui doivent attendre pendant l'étape liée à l'attachement de couleur sont celles +ayant trait à l'écriture. Ces paramètres permettent de faire attendre la transition jusqu'à ce qu'elle +soit possible, ce qui correspond au moment où la passe accède à cet attachement puisqu'elle est elle-même configurée +pour attendre ce moment. + +```c++ +renderPassInfo.dependencyCount = 1; +renderPassInfo.pDependencies = &dependency; +``` + +Nous fournissons enfin à la structure ayant trait à la render pass un tableau de configurations pour les subpass +dependencies. + +## Présentation + +La dernière étape pour l'affichage consiste à envoyer le résultat à la swap chain. La présentation est configurée avec +une structure de type `VkPresentInfoKHR`, et nous ferons cela à la fin de la fonction `drawFrame`. + +```c++ +VkPresentInfoKHR presentInfo{}; +presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + +presentInfo.waitSemaphoreCount = 1; +presentInfo.pWaitSemaphores = signalSemaphores; +``` + +Les deux premiers paramètres permettent d'indiquer les sémaphores devant signaler que la présentation peut se dérouler. + +```c++ +VkSwapchainKHR swapChains[] = {swapChain}; +presentInfo.swapchainCount = 1; +presentInfo.pSwapchains = swapChains; +presentInfo.pImageIndices = &imageIndex; +``` + +Les deux paramètres suivants fournissent un tableau contenant notre unique swap chain qui présentera les images et +l'indice de l'image pour celle-ci. + +```c++ +presentInfo.pResults = nullptr; // Optionnel +``` + +Ce dernier paramètre est optionnel. Il vous permet de fournir un tableau de `VkResult` que vous pourrez consulter pour +vérifier que toutes les swap chain ont bien présenté leur image sans problème. Cela n'est pas nécessaire dans notre +cas, car n'utilisant qu'une seule swap chain nous pouvons simplement regarder la valeur de retour de la fonction de +présentation. + +```c++ +vkQueuePresentKHR(presentQueue, &presentInfo); +``` + +La fonction `vkQueuePresentKHR` émet la requête de présentation d'une image par la swap chain. Nous ajouterons la +gestion des erreurs pour `vkAcquireNextImageKHR` et `vkQueuePresentKHR` dans le prochain chapitre car une erreur à ces +étapes n'implique pas forcément que le programme doit se terminer, mais plutôt qu'il doit s'adapter à des changements. + +Si vous avez fait tout ça correctement vous devriez avoir quelque chose comme cela à l'écran quand vous lancez votre +programme : + +![](/images/triangle.png) + +Enfin! Malheureusement si vous essayez de quitter proprement le programme vous obtiendrez un crash et un message +semblable à ceci : + +![](/images/semaphore_in_use.png) + +N'oubliez pas que puisque les opérations dans `drawFrame` sont asynchrones il est quasiment certain que lorsque vous +quittez le programme, celui-ci exécute encore des instructions et cela implique que vous essayez de libérer des +ressources en train d'être utilisées. Ce qui est rarement une bonne idée, surtout avec du bas niveau comme Vulkan. + +Pour régler ce problème nous devons attendre que le logical device finisse l'opération qu'il est en train de +réaliser avant de quitter `mainLoop` et de détruire la fenêtre : + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); +} +``` + +Vous pouvez également attendre la fin d'une opération quelconque depuis une queue spécifique à l'aide de la fonction +`vkQueueWaitIdle`. Ces fonction peuvent par ailleurs être utilisées pour réaliser une synchronisation très basique, +mais très inefficace. Le programme devrait maintenant se terminer sans problème quand vous fermez la fenêtre. + +## Frames en vol + +Si vous lancez l'application avec les validation layers maintenant, vous pouvez soit avoir des erreurs soit vous remarquerez +que l'utilisation de la mémoire augmente, lentement mais sûrement. La raison est que l'application soumet rapidement du +travail dans la fonction `drawframe`, mais que l'on ne vérifie pas si ces rendus sont effectivement terminés. +Si le CPU envoie plus de commandes que le GPU ne peut en exécuter, ce qui est le cas car nous envoyons nos command buffers +de manière totalement débridée, la queue de graphismes va progressivement se remplir de travail à effectuer. +Pire encore, nous utilisons `imageAvailableSemaphore` et `renderFinishedSemaphore` ainsi que nos command buffers pour +plusieurs frames en même temps. + +Le plus simple est d'attendre que le logical device n'aie plus de travail à effectuer avant de lui en envoyer de +nouveau, par exemple à l'aide de `vkQueueIdle` : + +```c++ +void drawFrame() { + ... + + vkQueuePresentKHR(presentQueue, &presentInfo); + + vkQueueWaitIdle(presentQueue); +} +``` + +Cependant cette méthode n'est clairement pas optimale pour le GPU car la pipeline peut en général gérer plusieurs images +à la fois grâce aux architectures massivement parallèles. Les étapes que l'image a déjà passées (par exemple le vertex +shader quand elle en est au fragment shader) peuvent tout à fait être utilisées pour l'image suivante. Nous +allons améliorer notre programme pour qu'il puisse supporter plusieurs images *en vol* (ou *in flight*) tout en +limitant la quantité de commandes dans la queue. + +Commencez par ajouter une constante en haut du programme qui définit le nombre de frames à traiter concurentiellement : + +```c++ +const int MAX_FRAMES_IN_FLIGHT = 2; +``` + +Chaque frame aura ses propres sémaphores : + +```c++ +std::vector imageAvailableSemaphores; +std::vector renderFinishedSemaphores; +``` + +La fonction `createSemaphores` doit être améliorée pour gérer la création de tout ceux-là : + +```c++ +void createSemaphores() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) { + + throw std::runtime_error("échec de la création des sémaphores d'une frame!"); + } +} +``` + +Ils doivent également être libérés à la fin du programme : + +```c++ +void cleanup() { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + + ... +} +``` + +Pour utiliser la bonne paire de sémaphores à chaque fois nous devons garder à portée de main l'indice de la frame en +cours. + +```c++ +size_t currentFrame = 0; +``` + +La fonction `drawFrame` peut maintenant être modifiée pour utiliser les bons objets : + +```c++ +void drawFrame() { + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + ... + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + + ... + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + + ... +} +``` + +Nous ne devons bien sûr pas oublier d'avancer à la frame suivante à chaque fois : + +```c++ +void drawFrame() { + ... + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +} +``` + +En utilisant l'opérateur de modulo `%` nous pouvons nous assurer que l'indice boucle à chaque fois que +`MAX_FRAMES_IN_FLIGHT` est atteint. + +Bien que nous ayons pas en place les objets facilitant le traitement de plusieurs frames simultanément, encore +maintenant le GPU traite plus de `MAX_FRAMES_IN_FLIGHT` à la fois. Nous n'avons en effet qu'une synchronisation GPU-GPU +mais pas de synchronisation CPU-GPU. Nous n'avons pas de moyen de savoir que le travail sur telle ou telle frame est +fini, ce qui a pour conséquence que nous pouvons nous retrouver à afficher une frame alors qu'elle est encore en +traitement. + +Pour la synchronisation CPU-GPU nous allons utiliser l'autre moyen fourni par Vulkan que nous avons déjà évoqué : les +*fences*. Au lieu d'informer une certaine opération que tel signal devra être attendu avant de continuer, ce que les +sémaphores permettent, les fences permettent au programme d'attendre l'exécution complète d'une opération. Nous allons +créer une fence pour chaque frame : + +```c++ +std::vector imageAvailableSemaphores; +std::vector renderFinishedSemaphores; +std::vector inFlightFences; +size_t currentFrame = 0; +``` + +J'ai choisi de créer les fences avec les sémaphores et de renommer la fonction `createSemaphores` en +`createSyncObjects` : + +```c++ +void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + + throw std::runtime_error("échec de la création des objets de synchronisation pour une frame!"); + } + } +} +``` + +La création d'une `VkFence` est très similaire à la création d'un sémaphore. N'oubliez pas de libérer les fences : + +```c++ +void cleanup() { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + ... +} +``` + +Nous voulons maintenant que `drawFrame` utilise les fences pour la synchronisation. L'appel à `vkQueueSubmit` inclut un +paramètre optionnel qui permet de passer une fence. Celle-ci sera informée de la fin de l'exécution du command buffer. +Nous pouvons interpréter ce signal comme la fin du rendu sur la frame. + +```c++ +void drawFrame() { + ... + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("échec de l'envoi d'un command buffer!"); + } + ... +} +``` + +La dernière chose qui nous reste à faire est de changer le début de `drawFrame` pour que la fonction attende le rendu de +la frame précédente : + +```c++ +void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + ... +} +``` + +La fonction `vkWaitForFences` prend en argument un tableau de fences. Elle attend soit qu'une seule fence soit que +toutes les fences déclarent être signalées avant de retourner. Le choix du mode d'attente se fait selon la valeur du +quatrième paramètre. Avec `VK_TRUE` nous demandons d'attendre toutes les fences, même si cela ne fait bien sûr pas de +différence vu que nous n'avons qu'une seule fence. Comme la fonction `vkAcquireNextImageKHR` cette fonction prend une +durée en argument, que nous ignorons. Nous devons ensuite réinitialiser les fences manuellement à l'aide d'un appel à +la fonction `vkResetFences`. + +Si vous lancez le programme maintenant vous allez constater un comportement étrange. Plus rien ne se passe. Nous attendons qu'une fence soit signalée alors qu'elle n'a +jamais été envoyée à aucune fonction. En effet les fences sont par défaut crées dans le +mode non signalé. Comme nous appelons `vkWaitForFences` avant `vkQueueSubmit` notre +première fence va créer une pause infinie. Pour empêcher cela nous devons initialiser +les fences dans le mode signalé, et ce dès leur création : + +```c++ +void createSyncObjects() { + ... + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + ... +} +``` + +La fuite de mémoire n'est plus, mais le programme ne fonctionne pas encore correctement. Si `MAX_FRAMES_IN_FLIGHT` est +plus grand que le nombre d'images de la swapchain ou que `vkAcquireNextImageKHR` ne retourne pas les images dans l'ordre, +alors il est possible que nous lancions le rendu dans une image qui est déjà *en vol*. Pour éviter ça, nous devons pour +chaque image de la swapchain si une frame en vol est en train d'utiliser celle-ci. Cette correspondance permettra de suivre +les images en vol par leur fences respective, de cette façon nous aurons immédiatement un objet de synchronisation à attendre +avant qu'une nouvelle frame puisse utiliser cette image. + +Tout d'abord, ajoutez une nouvelle liste nommée `imagesInFlight`: + +```c++ +std::vector inFlightFences; +std::vector imagesInFlight; +size_t currentFrame = 0; +``` + +Préparez-la dans `createSyncObjects`: + +```c++ +void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + imagesInFlight.resize(swapChainImages.size(), VK_NULL_HANDLE); + + ... +} +``` + +Initialement aucune frame n'utilise d'image, donc on peut explicitement l'initialiser à *pas de fence*. Maintenant, nous allons modifier +`drawFrame` pour attendre la fin de n'importe quelle frame qui serait en train d'utiliser l'image qu'on nous assigné pour la nouvelle frame. + +```c++ +void drawFrame() { + ... + + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + // Vérifier si une frame précédente est en train d'utiliser cette image (il y a une fence à attendre) + if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) { + vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX); + } + // Marque l'image comme étant à nouveau utilisée par cette frame + imagesInFlight[imageIndex] = inFlightFences[currentFrame]; + + ... +} +``` + +Parce que nous avons maintenant plus d'appels à `vkWaitForFences`, les appels à `vkResetFences` doivent être **déplacés**. Le mieux reste +de simplement l'appeler juste avant d'utiliser la fence: + +```c++ +void drawFrame() { + ... + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("échec de l'envoi d'un command buffer!"); + } + + ... +} +``` + +Nous avons implémenté tout ce qui est nécessaire à la synchronisation pour certifier qu'il n'y a pas plus de deux frames de travail +dans la queue et que ces frames n'utilise pas accidentellement la même image. Notez qu'il est tout à fait normal pour d'autre parties du code, +comme le nettoyage final, de se reposer sur des mécanismes de synchronisation plus durs comme `vkDeviceWaitIdle`. Vous devriez décider +de la bonne approche à utiliser en vous basant sur vos besoins de performances. + +Pour en apprendre plus sur la synchronisation rendez vous sur +[ces exemples complets](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#swapchain-image-acquire-and-present) +par Khronos. + +## Conclusion + +Un peu plus de 900 lignes plus tard nous avons enfin atteint le niveau où nous voyons des résultats à l'écran!! +Créer un programme avec Vulkan est clairement un énorme travail, mais grâce au contrôle que cet API vous offre vous +pouvez obtenir des performances énormes. Je ne peux que vous recommander de relire tout ce code et de vous assurer que +vous visualisez bien tout les éléments mis en jeu. Nous allons maintenant construire sur ces acquis pour étendre les +fonctionnalités de ce programme. + +Dans le prochain chapitre nous allons voir une autre petite chose nécessaire à tout bon programme Vulkan. + +[Code C++](/code/15_hello_triangle.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git "a/fr/03_Dessiner_un_triangle/04_Recr\303\251ation_de_la_swap_chain.md" "b/fr/03_Dessiner_un_triangle/04_Recr\303\251ation_de_la_swap_chain.md" new file mode 100644 index 00000000..175ae235 --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/04_Recr\303\251ation_de_la_swap_chain.md" @@ -0,0 +1,278 @@ +## Introduction + +Notre application nous permet maintenant d'afficher correctement un triangle, mais certains cas de figures ne sont pas +encore correctement gérés. Il est possible que la surface d'affichage soit redimensionnée par l'utilisateur et que la +swap chain ne soit plus parfaitement compatible. Nous devons faire en sorte d'être informés de tels changements pour +pouvoir recréer la swap chain. + +## Recréer la swap chain + +Créez la fonction `recreateSwapChain` qui appelle `createSwapChain` et toutes les fonctions de création d'objets +dépendants de la swap chain ou de la taille de la fenêtre. + +```c++ +void recreateSwapChain() { + vkDeviceWaitIdle(device); + + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandBuffers(); +} +``` + +Nous appelons d'abord `vkDeviceIdle` car nous ne devons surtout pas toucher à des ressources en cours d'utilisation. La +première chose à faire est bien sûr de recréer la swap chain. Les image views doivent être recrées également car +elles dépendent des images de la swap chain. La render pass doit être recrée car elle dépend du format des images de +la swap chain. Il est rare que le format des images de la swap chain soit altéré mais il n'est pas officiellement +garanti qu'il reste le même, donc nous gérerons ce cas là. La pipeline dépend de la taille des images pour la +configuration des rectangles de viewport et de ciseau, donc nous devons recréer la pipeline graphique. Il est possible +d'éviter cela en faisant de la taille de ces rectangles des états dynamiques. Finalement, les framebuffers et les +command buffers dépendent des images de la swap chain. + +Pour être certains que les anciens objets sont bien détruits avant d'en créer de nouveaux, nous devrions créer une +fonction dédiée à cela et que nous appellerons depuis `recreateSwapChain`. Créez donc `cleanupSwapChain` : + +```c++ +void cleanupSwapChain() { + +} + +void recreateSwapChain() { + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandBuffers(); +} +``` + +Nous allons déplacer le code de suppression depuis `cleanup` jusqu'à `cleanupSwapChain` : + +```c++ +void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); +} +``` + +Nous pouvons ensuite appeler cette nouvelle fonction depuis `cleanup` pour éviter la redondance de code : + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +Nous pourrions recréer la command pool à partir de rien mais ce serait du gâchis. J'ai préféré libérer les command +buffers existants à l'aide de la fonction `vkFreeCommandBuffers`. Nous pouvons de cette manière réutiliser la même +command pool mais changer les command buffers. + +Pour bien gérer le redimensionnement de la fenêtre nous devons récupérer la taille actuelle du framebuffer qui lui est +associé pour s'assurer que les images de la swap chain ont bien la nouvelle taille. Pour cela changez +`chooseSwapExtent` afin que cette fonction prenne en compte la nouvelle taille réelle : + +```c++ +VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + ... + } +} +``` + +C'est tout ce que nous avons à faire pour recréer la swap chain! Le problème cependant est que nous devons arrêter +complètement l'affichage pendant la recréation alors que nous pourrions éviter que les frames en vol soient perdues. +Pour cela vous devez passer l'ancienne swap chain en paramètre à `oldSwapChain` dans la structure +`VkSwapchainCreateInfoKHR` et détruire cette ancienne swap chain dès que vous ne l'utilisez plus. + +## Swap chain non-optimales ou dépassées + +Nous devons maintenant déterminer quand recréer la swap chain et donc quand appeler `recreateSwapChain`. Heureusement +pour nous Vulkan nous indiquera quand la swap chain n'est plus adéquate au moment de la présentation. Les fonctions +`vkAcquireNextImageKHR` et `vkQueuePresentKHR` peuvent pour cela retourner les valeurs suivantes : + +* `VK_ERROR_OUT_OF_DATE_KHR` : la swap chain n'est plus compatible avec la surface de fenêtre et ne peut plus être +utilisée pour l'affichage, ce qui arrive en général avec un redimensionnement de la fenêtre +* `VK_SUBOPTIMAL_KHR` : la swap chain peut toujours être utilisée pour présenter des images avec succès, mais les +caractéristiques de la surface de fenêtre ne correspondent plus à celles de la swap chain + +```c++ +VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + +if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; +} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("échec de la présentation d'une image à la swap chain!"); +} +``` + +Si la swap chain se trouve être dépassée quand nous essayons d'acquérir une nouvelle image il ne nous est plus possible +de présenter un quelconque résultat. Nous devons de ce fait aussitôt recréer la swap chain et tenter la présentation +avec la frame suivante. + +Vous pouvez aussi décider de recréer la swap chain si sa configuration n'est plus optimale, mais j'ai choisi de ne pas +le faire ici car nous avons de toute façon déjà acquis l'image. Ainsi `VK_SUCCES` et `VK_SUBOPTIMAL_KHR` sont considérés +comme des indicateurs de succès. + +```c++ +result = vkQueuePresentKHR(presentQueue, &presentInfo); + +if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + recreateSwapChain(); +} else if (result != VK_SUCCESS) { + throw std::runtime_error("échec de la présentation d'une image!"); +} + +currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +``` + +La fonction `vkQueuePresentKHR` retourne les mêmes valeurs avec la même signification. Dans ce cas nous recréons la +swap chain si elle n'est plus optimale car nous voulons les meilleurs résultats possibles. + +## Explicitement gérer les redimensionnements + +Bien que la plupart des drivers émettent automatiquement le code `VK_ERROR_OUT_OF_DATE_KHR` après qu'une fenêtre est +redimensionnée, cela n'est pas garanti par le standard. Par conséquent nous devons explictement gérer ces cas de +figure. Ajoutez une nouvelle variable qui indiquera que la fenêtre a été redimensionnée : + +```c++ +std::vector inFlightFences; +size_t currentFrame = 0; + +bool framebufferResized = false; +``` + +La fonction `drawFrame` doit ensuite être modifiée pour prendre en compte cette nouvelle variable : + +```c++ +if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); +} else if (result != VK_SUCCESS) { + ... +} +``` + +Il est important de faire cela après `vkQueuePresentKHR` pour que les sémaphores soient dans un état correct. Pour +détecter les redimensionnements de la fenêtre nous n'avons qu'à mettre en place `glfwSetFrameBufferSizeCallback` +qui nous informera d'un changement de la taille associée à la fenêtre : + +```c++ +void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); +} + +static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + +} +``` + +Nous devons utiliser une fonction statique car GLFW ne sait pas correctement appeler une fonction membre d'une classe +avec `this`. + +Nous récupérons une référence à la `GLFWwindow` dans la fonction de rappel que nous fournissons. De plus nous pouvons +paramétrer un pointeur de notre choix qui sera accessible à toutes nos fonctions de rappel. Nous pouvons y mettre la +classe elle-même. + +```c++ +window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +glfwSetWindowUserPointer(window, this); +glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); +``` + +De cette manière nous pouvons changer la valeur de la variable servant d'indicateur des redimensionnements : + +```c++ +static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; +} +``` + +Lancez maintenant le programme et changez la taille de la fenêtre pour voir si tout se passe comme prévu. + +## Gestion de la minimisation de la fenêtre + +Il existe un autre cas important où la swap chain peut devenir invalide : si la fenêtre est minimisée. Ce cas est +particulier car il résulte en un framebuffer de taille `0`. Dans ce tutoriel nous mettrons en pause le programme +jusqu'à ce que la fenêtre soit remise en avant-plan. À ce moment-là nous recréerons la swap chain. + +```c++ +void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + ... +} +``` + +L'appel initial à `glfwGetFramebufferSize` prend en charge le cas où la taille est déjà correcte et `glfwWaitEvents` n'aurait rien à attendre. + +Félicitations, vous avez codé un programme fonctionnel avec Vulkan! Dans le prochain chapitre nous allons supprimer les +sommets du vertex shader et mettre en place un vertex buffer. + +[Code C++](/code/16_swap_chain_recreation.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git "a/fr/04_Vertex_buffers/00_Description_des_entr\303\251es_des_sommets.md" "b/fr/04_Vertex_buffers/00_Description_des_entr\303\251es_des_sommets.md" new file mode 100644 index 00000000..58a4d6f5 --- /dev/null +++ "b/fr/04_Vertex_buffers/00_Description_des_entr\303\251es_des_sommets.md" @@ -0,0 +1,217 @@ +## Introduction + +Dans les quatre prochains chapitres nous allons remplacer les sommets inscrits dans le vertex shader par un vertex +buffer stocké dans la mémoire de la carte graphique. Nous commencerons par une manière simple de procéder en créant un +buffer manipulable depuis le CPU et en y copiant des données avec `memcpy`. Puis nous verrons comment avantageusement +utiliser un *staging buffer* pour accéder à de la mémoire de haute performance. + +## Vertex shader + +Premièrement, changeons le vertex shader en retirant les coordonnées des sommets de son code. Elles seront maintenant +stockés dans une variable. Elle sera liée au contenu du vertex buffer, ce qui est indiqué par le mot-clef `in`. Faisons +de même avec la couleur. + +```glsl +#version 450 + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +out gl_PerVertex { + vec4 gl_Position; +}; + +void main() { + gl_Position = vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} +``` + +Les variables `inPosition` et `inColor` sont des *vertex attributes*. Ce sont des propriétés spécifiques du sommet à +l'origine de l'invocation du shader. Ces données peuvent être de différentes natures, des couleurs aux coordonnées en +passant par des coordonnées de texture. Recompilez ensuite le vertex shader. + +Tout comme pour `fragColor`, les annotations de type `layout(location=x)` assignent un indice à l'entrée. Cet indice +est utilisé depuis le code C++ pour les reconnaître. Il est important de savoir que certains types - comme les vecteurs +de flottants de double précision (64 bits) - prennent deux emplacements. Voici un exemple d'une telle situation, où il +est nécessaire de prévoir un écart entre deux entrés : + +```glsl +layout(location = 0) in dvec3 inPosition; +layout(location = 2) in vec3 inColor; +``` + +Vous pouvez trouver plus d'information sur les qualificateurs d'organisation sur +[le wiki](https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)). + +## Sommets + +Nous déplaçons les données des sommets depuis le code du shader jusqu'au code C++. Commencez par inclure la librairie +GLM, afin d'utiliser des vecteurs et des matrices. Nous allons utiliser ces types pour les vecteurs de position et de +couleur. + +```c++ +#include +``` + +Créez une nouvelle structure appelée `Vertex`. Elle possède deux attributs que nous utiliserons pour le vertex shader : + +```c++ +struct Vertex { + glm::vec2 pos; + glm::vec3 color; +}; +``` + +GLM nous fournit des types très pratiques simulant les types utilisés par GLSL. + +```c++ +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; +``` + +Nous utiliserons ensuite un tableau de structures pour représenter un ensemble de sommets. Nous utiliserons les mêmes +couleurs et les mêmes positions qu'avant, mais elles seront combinées en un seul tableau d'objets. + +## Lier les descriptions + +La prochaine étape consiste à indiquer à Vulkan comment passer ces données au shader une fois qu'elles sont +stockées dans le GPU. Nous verrons plus tard comment les y stocker. Il y a deux types de structures que nous allons +devoir utiliser. + +Pour la première, appelée `VkVertexInputBindingDescription`, nous allons ajouter une fonction à `Vertex` qui renverra +une instance de cette structure. + +```c++ +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + + return bindingDescription; + } +}; +``` + +Un *vertex binding* décrit la lecture des données stockées en mémoire. Elle fournit le nombre d'octets entre les jeux de +données et la manière de passer d'un ensemble de données (par exemple une coordonnée) au suivant. Elle permet à Vulkan +de savoir comment extraire chaque jeu de données correspondant à une invocation du vertex shader du vertex buffer. + +```c++ +VkVertexInputBindingDescription bindingDescription{}; +bindingDescription.binding = 0; +bindingDescription.stride = sizeof(Vertex); +bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; +``` + +Nos données sont compactées en un seul tableau, nous n'aurons besoin que d'un seul vertex binding. Le membre `binding` +indique l'indice du vertex binding dans le tableau des bindings. Le paramètre `stride` fournit le nombre d'octets +séparant les débuts de deux ensembles de données, c'est à dire l'écart entre les données devant ếtre fournies à une +invocation de vertex shader et celles devant être fournies à la suivante. Enfin `inputRate` peut prendre les valeurs +suivantes : + +* `VK_VERTEX_INPUT_RATE_VERTEX` : Passer au jeu de données suivante après chaque sommet +* `VK_VERTEX_INPUT_RATE_INSTANCE` : Passer au jeu de données suivantes après chaque instance + +Nous n'utilisons pas d'*instanced rendering* donc nous utiliserons `VK_VERTEX_INPUT_RATE_VERTEX`. + +## Description des attributs + +La seconde structure dont nous avons besoin est `VkVertexInputAttributeDescription`. Nous allons également en créer deux +instances depuis une fonction membre de `Vertex` : + +```c++ +#include + +... + +static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + return attributeDescriptions; +} +``` + +Comme le prototype le laisse entendre, nous allons avoir besoin de deux de ces structures. Elles décrivent chacunes +l'origine et la nature des données stockées dans une variable shader annotée du `location=x`, et la manière d'en +déterminer les valeurs depuis les données extraites par le binding. Comme nous avons deux de +ces variables, nous avons besoin de deux de ces structures. Voici ce qu'il faut remplir pour la position. + +```c++ +attributeDescriptions[0].binding = 0; +attributeDescriptions[0].location = 0; +attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; +attributeDescriptions[0].offset = offsetof(Vertex, pos); +``` + +Le paramètre `binding` informe Vulkan de la provenance des données du sommet qui mené à l'invocation du vertex shader, +en lui fournissant le vertex binding qui les a extraites. Le paramètre `location` correspond à la valeur donnée à la +directive `location` dans le code du vertex shader. Dans notre cas l'entrée `0` correspond à la position du sommet +stockée dans un vecteur de floats de 32 bits. + +Le paramètre `format` permet donc de décrire le type de donnée de l'attribut. Étonnement les formats doivent être +indiqués avec des valeurs énumérées dont les noms semblent correspondre à des gradients de couleur : + +* `float` : `VK_FORMAT_R32_SFLOAT` +* `vec2` : `VK_FORMAT_R32G32_SFLOAT` +* `vec3` : `VK_FORMAT_R32G32B32_SFLOAT` +* `vec4` : `VK_FORMAT_R32G32B32A32_SFLOAT` + +Comme vous pouvez vous en douter il faudra utiliser le format dont le nombre de composants de couleurs correspond au +nombre de données à transmettre. Il est autorisé d'utiliser plus de données que ce qui est prévu dans le shader, et ces +données surnuméraires seront silencieusement ignorées. Si par contre il n'y a pas assez de valeurs les valeurs suivantes +seront utilisées par défaut pour les valeurs manquantes : 0, 0 et 1 pour les deuxième, troisième et quatrième +composantes. Il n'y a pas de valeur par défaut pour le premier membre car ce cas n'est pas autorisé. Les types +(`SFLOAT`, `UINT` et `SINT`) et le nombre de bits doivent par contre correspondre parfaitement à ce qui est indiqué dans +le shader. Voici quelques exemples : + +* `ivec2` correspond à `VK_FORMAT_R32G32_SINT` et est un vecteur à deux composantes d'entiers signés de 32 bits +* `uvec4` correspond à `VK_FORMAT_R32G32B32A32_UINT` et est un vecteur à quatre composantes d'entiers non signés de 32 +bits +* `double` correspond à `VK_FORMAT_R64_SFLOAT` et est un float à précision double (donc de 64 bits) + +Le paramètre `format` définit implicitement la taille en octets des données. Mais le binding extrait dans notre cas deux +données pour chaque sommet : la position et la couleur. Pour savoir quels octets doivent être mis dans la variable à +laquelle la structure correspond, le paramètre `offset` permet d'indiquer de combien d'octets il faut se décaler dans +les données extraites pour se trouver au début de la variable. Ce décalage est calculé automatiquement par la macro +`offsetof`. + +L'attribut de couleur est décrit de la même façon. Essayez de le remplir avant de regarder la solution ci-dessous. + +```c++ +attributeDescriptions[1].binding = 0; +attributeDescriptions[1].location = 1; +attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; +attributeDescriptions[1].offset = offsetof(Vertex, color); +``` + +## Entrée des sommets dans la pipeline + +Nous devons maintenant mettre en place la réception par la pipeline graphique des données des sommets. Nous allons +modifier une structure dans `createGraphicsPipeline`. Trouvez `vertexInputInfo` et ajoutez-y les références aux deux +structures de description que nous venons de créer : + +```c++ +auto bindingDescription = Vertex::getBindingDescription(); +auto attributeDescriptions = Vertex::getAttributeDescriptions(); + +vertexInputInfo.vertexBindingDescriptionCount = 1; +vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); +vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; +vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); +``` + +La pipeline peut maintenant accepter les données des vertices dans le format que nous utilisons et les fournir au vertex +shader. Si vous lancez le programme vous verrez que les validation layers rapportent qu'aucun vertex buffer n'est mis +en place. Nous allons donc créer un vertex buffer et y placer les données pour que le GPU puisse les utiliser. + +[Code C++](/code/17_vertex_input.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git "a/fr/04_Vertex_buffers/01_Cr\303\251ation_de_vertex_buffers.md" "b/fr/04_Vertex_buffers/01_Cr\303\251ation_de_vertex_buffers.md" new file mode 100644 index 00000000..263d587e --- /dev/null +++ "b/fr/04_Vertex_buffers/01_Cr\303\251ation_de_vertex_buffers.md" @@ -0,0 +1,315 @@ +## Introduction + +Les buffers sont pour Vulkan des emplacements mémoire qui peuvent permettre de stocker des données quelconques sur la +carte graphique. Nous pouvons en particulier y placer les données représentant les sommets, et c'est ce que nous allons +faire dans ce chapitre. Nous verrons plus tard d'autres utilisations répandues. Au contraire des autres objets que nous +avons rencontré les buffers n'allouent pas eux-mêmes de mémoire. Il nous faudra gérer la mémoire à la main. + +## Création d'un buffer + +Créez la fonction `createVertexBuffer` et appelez-la depuis `initVulkan` juste avant `createCommandBuffers`. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); +} + +... + +void createVertexBuffer() { + +} +``` + +Pour créer un buffer nous allons devoir remplir une structure de type `VkBufferCreateInfo`. + +```c++ +VkBufferCreateInfo bufferInfo{}; +bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; +bufferInfo.size = sizeof(vertices[0]) * vertices.size(); +``` + +Le premier champ de cette structure s'appelle `size`. Il spécifie la taille du buffer en octets. Nous pouvons utiliser +`sizeof` pour déterminer la taille de notre tableau de valeur. + +```c++ +bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; +``` + +Le deuxième champ, appelé `usage`, correspond à l'utilisation type du buffer. Nous pouvons indiquer plusieurs valeurs +représentant les utilisations possibles. Dans notre cas nous ne mettons que la valeur qui correspond à un vertex buffer. + +```c++ +bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; +``` + +De la même manière que les images de la swap chain, les buffers peuvent soit être gérés par une queue family, ou bien +être partagés entre plusieurs queue families. Notre buffer ne sera utilisé que par la queue des graphismes, nous +pouvons donc rester en mode exclusif. + +Le paramètre `flags` permet de configurer le buffer tel qu'il puisse être constitué de plusieurs emplacements distincts +dans la mémoire. Nous n'utiliserons pas cette fonctionnalité, laissez `flags` à `0`. + +Nous pouvons maintenant créer le buffer en appelant `vkCreateBuffer`. Définissez un membre donnée pour stocker ce +buffer : + +```c++ +VkBuffer vertexBuffer; + +... + +void createVertexBuffer() { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(vertices[0]) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) { + throw std::runtime_error("echec de la creation d'un vertex buffer!"); + } +} +``` + +Le buffer doit être disponible pour toutes les opérations de rendu, nous ne pouvons donc le détruire qu'à la fin du +programme, et ce dans `cleanup` car il ne dépend pas de la swap chain. + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + + ... +} +``` + +## Fonctionnalités nécessaires de la mémoire + +Le buffer a été créé mais il n'est lié à aucune forme de mémoire. La première étape de l'allocation de mémoire consiste +à récupérer les fonctionnalités dont le buffer a besoin à l'aide de la fonction `vkGetBufferMemoryRequirements`. + +```c++ +VkMemoryRequirements memRequirements; +vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements); +``` + +La structure que la fonction nous remplit possède trois membres : + +* `size` : le nombre d'octets dont le buffer a besoin, ce qui peut différer de ce que nous avons écrit en préparant le +buffer +* `alignment` : le décalage en octets entre le début de la mémoire allouée pour lui et le début des données du buffer, +ce que le driver détermine avec les valeurs que nous avons fournies dans `usage` et `flags` +* `memoryTypeBits` : champs de bits combinant les types de mémoire qui conviennent au buffer + +Les cartes graphiques offrent plusieurs types de mémoire. Ils diffèrent en performance et en opérations disponibles. +Nous devons considérer ce dont le buffer a besoin en même temps que ce dont nous avons besoin pour sélectionner le +meilleur type de mémoire possible. Créons une fonction `findMemoryType` pour y isoler cette logique. + +```c++ +uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + +} +``` + +Nous allons commencer cette fonction en récupérant les différents types de mémoire que la carte graphique peut nous +offrir. + +```c++ +VkPhysicalDeviceMemoryProperties memProperties; +vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); +``` + +La structure `VkPhysicalDeviceMemoryProperties` comprend deux tableaux appelés `memoryHeaps` et `memoryTypes`. Une pile +de mémoire (memory heap en anglais) correspond aux types physiques de mémoire. Par exemple la VRAM est une pile, de même +que la RAM utilisée comme zone de swap si la VRAM est pleine en est une autre. Tous les autres types de mémoire stockés +dans `memoryTypes` sont répartis dans ces piles. Nous n'allons pas utiliser la pile comme facteur de choix, mais vous +pouvez imaginer l'impact sur la performance que cette distinction peut avoir. + +Trouvons d'abord un type de mémoire correspondant au buffer : + +```c++ +for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if (typeFilter & (1 << i)) { + return i; + } +} + +throw std::runtime_error("aucun type de memoire ne satisfait le buffer!"); +``` + +Le paramètre `typeFilter` nous permettra d'indiquer les types de mémoire nécessaires au buffer lors de l'appel à la +fonction. Ce champ de bit voit son n-ième bit mis à `1` si le n-ième type de mémoire disponible lui convient. Ainsi +nous pouvons itérer sur les bits de `typeFilter` pour trouver les types de mémoire qui lui correspondent. + +Cependant cette vérification ne nous est pas suffisante. Nous devons vérifier que la mémoire est accesible depuis le CPU +afin de pouvoir y écrire les données des vertices. Nous devons pour cela vérifier que le champ de bits `properyFlags` +comprend au moins `VK_MEMORY_PROPERTY_HOSY_VISIBLE_BIT`, de même que `VK_MEMORY_PROPERTY_HOSY_COHERENT_BIT`. Nous +verrons pourquoi cette deuxième valeur est nécessaire quand nous lierons de la mémoire au buffer. + +Nous placerons ces deux valeurs dans le paramètre `properties`. Nous pouvons changer la boucle pour qu'elle prenne en +compte le champ de bits : + +```c++ +for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } +} +``` + +Le ET bit à bit fournit une valeur non nulle si et seulement si au moins l'une des propriétés est supportée. Nous ne +pouvons nous satisfaire de cela, c'est pourquoi il est nécessaire de comparer le résultat au champ de bits complet. Si +ce résultat nous convient, nous pouvons retourner l'indice de la mémoire et utiliser cet emplacement. Si aucune mémoire +ne convient nous levons une exception. + +## Allocation de mémoire + +Maintenant que nous pouvons déterminer un type de mémoire nous convenant, nous pouvons y allouer de la mémoire. Nous +devons pour cela remplir la structure `VkMemoryAllocateInfo`. + +```c++ +VkMemoryAllocateInfo allocInfo{}; +allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; +allocInfo.allocationSize = memRequirements.size; +allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); +``` + +Pour allouer de la mémoire il nous suffit d'indiquer une taille et un type, ce que nous avons déjà déterminé. Créez un +membre donnée pour contenir la référence à l'espace mémoire et allouez-le à l'aide de `vkAllocateMemory`. + +```c++ +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; + +... +if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) { + throw std::runtime_error("echec d'une allocation de memoire!"); +} +``` + +Si l'allocation a réussi, nous pouvons associer cette mémoire au buffer avec la fonction `vkBindBufferMemory` : + +```c++ +vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0); +``` + +Les trois premiers paramètres sont évidents. Le quatrième indique le décalage entre le début de la mémoire et le début +du buffer. Nous avons alloué cette mémoire spécialement pour ce buffer, nous pouvons donc mettre `0`. Si vous décidez +d'allouer un grand espace mémoire pour y mettre plusieurs buffers, sachez qu'il faut que ce nombre soit divisible par +`memRequirements.alignement`. Notez que cette stratégie est la manière recommandée de gérer la mémoire des GPUs (voyez +[cet article](https://developer.nvidia.com/vulkan-memory-management)). + +Il est évident que cette allocation dynamique de mémoire nécessite que nous libérions l'emplacement nous-mêmes. Comme la +mémoire est liée au buffer, et que le buffer sera nécessaire à toutes les opérations de rendu, nous ne devons la libérer +qu'à la fin du programme. + + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); +``` + +## Remplissage du vertex buffer + +Il est maintenant temps de placer les données des vertices dans le buffer. Nous allons +[mapper la mémoire](https://en.wikipedia.org/wiki/Memory-mapped_I/O) dans un emplacement accessible par le CPU à l'aide +de la fonction `vkMapMemory`. + +```c++ +void* data; +vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data); +``` + +Cette fonction nous permet d'accéder à une région spécifique d'une ressource. Nous devons pour cela indiquer un décalage +et une taille. Nous mettons ici respectivement `0` et `bufferInfo.size`. Il est également possible de fournir la valeur +`VK_WHOLE_SIZE` pour mapper d'un coup toute la ressource. L'avant-dernier paramètre est un champ de bits pour l'instant +non implémenté par Vulkan. Il est impératif de la laisser à `0`. Enfin, le dernier paramètre permet de fournir un +pointeur vers la mémoire ainsi mappée. + +```c++ +void* data; +vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferInfo.size); +vkUnmapMemory(device, vertexBufferMemory); +``` + +Vous pouvez maintenant utiliser `memcpy` pour copier les vertices dans la mémoire, puis démapper le buffer à l'aide de +`vkUnmapMemory`. Malheureusement le driver peut décider de cacher les données avant de les copier dans le buffer. Il est +aussi possible que les données soient copiées mais que ce changement ne soit pas visible immédiatement. Il y a deux +manières de régler ce problème : + +* Utiliser une pile de mémoire cohérente avec la RAM, ce qui est indiqué par `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` +* Appeler `vkFlushMappedMemoryRanges` après avoir copié les données, puis appeler `vkInvalidateMappedMemory` avant +d'accéder à la mémoire + +Nous utiliserons la première approche qui nous assure une cohérence permanente. Cette méthode est moins performante que +le flushing explicite, mais nous verrons dès le prochain chapitre que cela n'a aucune importance car nous changerons +complètement de stratégie. + +Par ailleurs, notez que l'utilisation d'une mémoire cohérente ou le flushing de la mémoire ne garantissent que le fait +que le driver soit au courant des modifications de la mémoire. La seule garantie est que le déplacement se finisse d'ici +le prochain appel à `vkQueueSubmit`. + +Remarquez également l'utilisation de `memcpy` qui indique la compatibilité bit-à-bit des structures avec la +représentation sur la carte graphique. + +## Lier le vertex buffer + +Il ne nous reste qu'à lier le vertex buffer pour les opérations de rendu. Nous allons pour cela compléter la fonction +`createCommandBuffers`. + +```c++ +vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + +VkBuffer vertexBuffers[] = {vertexBuffer}; +VkDeviceSize offsets[] = {0}; +vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + +vkCmdDraw(commandBuffers[i], static_cast(vertices.size()), 1, 0, 0); +``` + +La fonction `vkCmdBindVertexBuffers` lie des vertex buffers aux bindings. Les deuxième et troisième paramètres indiquent +l'indice du premier binding auquel le buffer correspond et le nombre de bindings qu'il contiendra. L'avant-dernier +paramètre est le tableau de vertex buffers à lier, et le dernier est un tableau de décalages en octets entre le début +d'un buffer et le début des données. Il est d'ailleurs préférable d'appeler `vkCmdDraw` avec la taille du tableau de +vertices plutôt qu'avec un nombre écrit à la main. + +Lancez maintenant le programme; vous devriez voir le triangle habituel apparaître à l'écran. + +![](/images/triangle.png) + +Essayez de colorer le vertex du haut en blanc et relancez le programme : + +```c++ +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 1.0f, 1.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; +``` + +![](/images/triangle_white.png) + +Dans le prochain chapitre nous verrons une autre manière de copier les données vers un buffer. Elle est plus performante +mais nécessite plus de travail. + +[Code C++](/code/18_vertex_buffer.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git "a/fr/04_Vertex_buffers/02_Buffer_interm\303\251diaire.md" "b/fr/04_Vertex_buffers/02_Buffer_interm\303\251diaire.md" new file mode 100644 index 00000000..b6f2fd2a --- /dev/null +++ "b/fr/04_Vertex_buffers/02_Buffer_interm\303\251diaire.md" @@ -0,0 +1,247 @@ +## Introduction + +Nous avons maintenant un vertex buffer fonctionnel. Par contre il n'est pas dans la mémoire la plus optimale posible +pour la carte graphique. Il serait préférable d'utiliser une mémoire `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`, +mais de telles mémoires ne sont pas accessibles depuis le CPU. Dans ce chapitre nous allons créer deux vertex buffers. +Le premier, un buffer intermédiaire (*staging buffer*), sera stocké dans de la mémoire accessible depuis le CPU, et +nous y mettrons nos données. Le second sera directement dans la carte graphique, et nous y copierons les données des +vertices depuis le buffer intermédiaire. + +## Queue de transfert + +La commande de copie des buffers provient d'une queue family qui supporte les opérations de transfert, ce qui est +indiqué par `VK_QUEUE_TRANFER_BIT`. Une bonne nouvelle : toute queue qui supporte les graphismes ou le calcul doit +supporter les transferts. Par contre il n'est pas obligatoire pour ces queues de l'indiquer dans le champ de bit qui les +décrit. + +Si vous aimez la difficulté, vous pouvez préférer l'utilisation d'une queue spécifique aux opérations de transfert. Vous +aurez alors ceci à changer : + +* Modifier la structure `QueueFamilyIndices` et la fonction `findQueueFamilies` pour obtenir une queue family dont la +description comprend `VK_QUEUE_TRANSFER_BIT` mais pas `VK_QUEUE_GRAPHICS_BIT` +* Modifier `createLogicalDevice` pour y récupérer une référence à une queue de transfert +* Créer une command pool pour les command buffers envoyés à la queue de transfert +* Changer la valeur de `sharingMode` pour les ressources qui le demandent à `VK_SHARING_MODE_CONCURRENT`, et indiquer à +la fois la queue des graphismes et la queue ds transferts +* Émettre toutes les commandes de transfert telles `vkCmdCopyBuffer` - nous allons l'utiliser dans ce chapitre - à la +queue de transfert au lieu de la queue des graphismes + +Cela représente pas mal de travail, mais vous en apprendrez beaucoup sur la gestion des resources entre les queue +families. + +## Abstraction de la création des buffers + +Comme nous allons créer plusieurs buffers, il serait judicieux de placer la logique dans une fonction. Appelez-la +`createBuffer` et déplacez-y le code suivant : + +```c++ +void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("echec de la creation d'un buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("echec de l'allocation de memoire!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); +} +``` + +Cette fonction nécessite plusieurs paramètres, tels que la taille du buffer, les propriétés dont nous avons besoin et +l'utilisation type du buffer. La fonction a deux résultats, elle fonctionne donc en modifiant la valeur des deux +derniers paramètres, dans lesquels elle place les référernces aux objets créés. + +Vous pouvez maintenant supprimer la création du buffer et l'allocation de la mémoire de `createVertexBuffer` et +remplacer tout ça par un appel à votre nouvelle fonction : + +```c++ +void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexBufferMemory); + + void* data; + vkMapMemory(device, vertexBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, vertexBufferMemory); +} +``` + +Lancez votre programme et assurez-vous que tout fonctionne toujours aussi bien. + +## Utiliser un buffer intermédiaire + +Nous allons maintenant faire en sorte que `createVertexBuffer` utilise d'abord un buffer visible pour copier les +données sur la carte graphique, puis qu'il utilise de la mémoire locale à la carte graphique pour le véritable buffer. + +```c++ +void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); +} +``` + +Nous utilisons ainsi un nouveau `stagingBuffer` lié à la `stagingBufferMemory` pour transmettre les données à la carte +graphique. Dans ce chapitre nous allons utiliser deux nouvelles valeurs pour les utilisations des buffers : + +* `VK_BUFFER_USAGE_TRANSFER_SCR_BIT` : le buffer peut être utilisé comme source pour un transfert de mémoire +* `VK_BUFFER_USAGE_TRANSFER_DST_BIT` : le buffer peut être utilisé comme destination pour un transfert de mémoire + +Le `vertexBuffer` est maintenant alloué à partir d'un type de mémoire local au device, ce qui implique en général que +nous ne pouvons pas utiliser `vkMapMemory`. Nous pouvons cependant bien sûr y copier les données depuis le buffer +intermédiaire. Nous pouvons indiquer que nous voulons transmettre des données entre ces buffers à l'aide des valeurs +que nous avons vues juste au-dessus. Nous pouvons combiner ces informations avec par exemple +`VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`. + +Nous allons maintenant écrire la fonction `copyBuffer`, qui servira à recopier le contenu du buffer intermédiaire dans +le véritable buffer. + +```c++ +void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + +} +``` + +Les opérations de transfert de mémoire sont réalisées à travers un command buffer, comme pour l'affichage. Nous devons +commencer par allouer des command buffers temporaires. Vous devriez d'ailleurs utiliser une autre command pool pour +tous ces command buffer temporaires, afin de fournir à l'implémentation une occasion d'optimiser la gestion de la +mémoire séparément des graphismes. Si vous le faites, utilisez `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` pendant la +création de la command pool, car les commands buffers ne seront utilisés qu'une seule fois. + +```c++ +void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); +} +``` + +Enregistrez ensuite le command buffer : + +```c++ +VkCommandBufferBeginInfo beginInfo{}; +beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; +beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + +vkBeginCommandBuffer(commandBuffer, &beginInfo); +``` + +Nous allons utiliser le command buffer une fois seulement, et attendre que la copie soit +terminée avant de sortir de la fonction. Il est alors préférable d'informer le driver de cela à l'aide de +`VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`. + +```c++ +VkBufferCopy copyRegion{}; +copyRegion.srcOffset = 0; // Optionnel +copyRegion.dstOffset = 0; // Optionnel +copyRegion.size = size; +vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); +``` + +La copie est réalisée à l'aide de la commande `vkCmdCopyBuffer`. Elle prend les buffers de source et d'arrivée comme +arguments, et un tableau des régions à copier. Ces régions sont décrites dans des structures de type `VkBufferCopy`, qui +consistent en un décalage dans le buffer source, le nombre d'octets à copier et le décalage dans le buffer d'arrivée. Il +n'est ici pas possible d'indiquer `VK_WHOLE_SIZE`. + +```c++ +vkEndCommandBuffer(commandBuffer); +``` + +Ce command buffer ne sert qu'à réaliser les copies des buffers, nous pouvons donc arrêter l'enregistrement dès +maintenant. Exécutez le command buffer pour compléter le transfert : + +```c++ +VkSubmitInfo submitInfo{}; +submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; +submitInfo.commandBufferCount = 1; +submitInfo.pCommandBuffers = &commandBuffer; + +vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); +vkQueueWaitIdle(graphicsQueue); +``` + +Au contraire des commandes d'affichage très complexes, il n'y a pas de synchronisation particulière à mettre en place. +Nous voulons simplement nous assurer que le transfert se réalise immédiatement. Deux possibilités s'offrent alors à +nous : utiliser une fence et l'attendre avec `vkWaitForFences`, ou simplement attendre avec `vkQueueWaitIdle` que la +queue des transfert soit au repos. Les fences permettent de préparer de nombreux transferts pour qu'ils s'exécutent +concurentiellement, et offrent au driver encore une manière d'optimiser le travail. L'autre méthode a l'avantage de la +simplicité. Implémentez le système de fence si vous le désirez, mais cela vous obligera à modifier l'organisation de ce +module. + +```c++ +vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); +``` + +N'oubliez pas de libérer le command buffer utilisé pour l'opération de transfert. + +Nous pouvons maintenant appeler `copyBuffer` depuis la fonction `createVertexBuffer` pour que les sommets soient enfin +stockées dans la mémoire locale. + +```c++ +createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + +copyBuffer(stagingBuffer, vertexBuffer, bufferSize); +``` + +Maintenant que les données sont dans la carte graphique, nous n'avons plus besoin du buffer intermédiaire, et devons +donc le détruire. + +```c++ + ... + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); +} +``` + +Lancez votre programme pour vérifier que vous voyez toujours le même triangle. L'amélioration n'est peut-être pas +flagrante, mais il est clair que la mémoire permet d'améliorer les performances, préparant ainsi le terrain +pour le chargement de géométrie plus complexe. + +## Conclusion + +Notez que dans une application réelle, vous ne devez pas allouer de la mémoire avec `vkAllocateMemory` pour chaque +buffer. De toute façon le nombre d'appel à cette fonction est limité, par exemple à 4096, et ce même sur des cartes +graphiques comme les GTX 1080. La bonne pratique consiste à allouer une grande zone de mémoire et d'utiliser un +gestionnaire pour créer des décalages pour chacun des buffers. Il est même préférable d'utiliser un buffer pour +plusieurs types de données (sommets et uniformes par exemple) et de séparer ces types grâce à des indices dans le +buffer (voyez encore [ce même article](https://developer.nvidia.com/vulkan-memory-management)). + +Vous pouvez implémenter votre propre solution, ou bien utiliser la librairie +[VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) crée par GPUOpen. Pour ce +tutoriel, ne vous inquiétez pas pour cela car nous n'atteindrons pas cette limite. + +[Code C++](/code/19_staging_buffer.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git a/fr/04_Vertex_buffers/03_Index_buffer.md b/fr/04_Vertex_buffers/03_Index_buffer.md new file mode 100644 index 00000000..2e933ad6 --- /dev/null +++ b/fr/04_Vertex_buffers/03_Index_buffer.md @@ -0,0 +1,151 @@ +## Introduction + +Les modèles 3D que vous serez susceptibles d'utiliser dans des applications réelles partagerons le plus souvent des +vertices communs à plusieurs triangles. Cela est d'ailleurs le cas avec un simple rectangle : + +![](/images/vertex_vs_index.svg) + +Un rectangle est composé de triangles, ce qui signifie que nous aurions besoin d'un vertex buffer avec 6 vertices. Mais +nous dupliquerions alors des vertices, aboutissant à un gachis de mémoire. Dans des modèles plus complexes, les vertices +sont en moyenne en contact avec 3 triangles, ce qui serait encore pire. La solution consiste à utiliser un index buffer. + +Un index buffer est essentiellement un tableau de références vers le vertex buffer. Il vous permet de réordonner ou de +dupliquer les données de ce buffer. L'image ci-dessus démontre l'utilité de cette méthode. + +## Création d'un index buffer + +Dans ce chapitre, nous allons ajouter les données nécessaires à l'affichage d'un rectangle. Nous allons ainsi rajouter +une coordonnée dans le vertex buffer et créer un index buffer. Voici les données des sommets au complet : + +```c++ +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; +``` + +Le coin en haut à gauche est rouge, celui en haut à droite est vert, celui en bas à droite est bleu et celui en bas à +gauche est blanc. Les couleurs seront dégradées par l'interpolation du rasterizer. Nous allons maintenant créer le +tableau `indices` pour représenter l'index buffer. Son contenu correspond à ce qui est présenté dans l'illustration. + +```c++ +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; +``` + +Il est possible d'utiliser `uint16_t` ou `uint32_t` pour les valeurs de l'index buffer, en fonction du nombre d'éléments +dans `vertices`. Nous pouvons nous contenter de `uint16_t` car nous n'utilisons pas plus de 65535 sommets différents. + +Comme les données des sommets, nous devons placer les indices dans un `VkBuffer` pour que le GPU puisse y avoir accès. +Créez deux membres donnée pour référencer les ressources du futur index buffer : + +```c++ +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; +VkBuffer indexBuffer; +VkDeviceMemory indexBufferMemory; +``` + +La fonction `createIndexBuffer` est quasiment identique à `createVertexBuffer` : + +```c++ +void initVulkan() { + ... + createVertexBuffer(); + createIndexBuffer(); + ... +} + +void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); +} +``` + +Il n'y a que deux différences : `bufferSize` correspond à la taille du tableau multiplié par `sizeof(uint16_t)`, et +`VK_BUFFER_USAGE_VERTEX_BUFFER_BIT` est remplacé par `VK_BUFFER_USAGE_INDEX_BUFFER_BIT`. À part ça tout est +identique : nous créons un buffer intermédiaire puis le copions dans le buffer final local au GPU. + +L'index buffer doit être libéré à la fin du programme depuis `cleanup`. + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + ... +} +``` + +## Utilisation d'un index buffer + +Pour utiliser l'index buffer lors des opérations de rendu nous devons modifier un petit peu `createCommandBuffers`. Tout +d'abord il nous faut lier l'index buffer. La différence est qu'il n'est pas possible d'avoir plusieurs index buffers. De +plus il n'est pas possible de subdiviser les sommets en leurs coordonnées, ce qui implique que la modification d'une +seule coordonnée nécessite de créer un autre sommet le vertex buffer. + +```c++ +vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + +vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); +``` + +Un index buffer est lié par la fonction `vkCmdBindIndexBuffer`. Elle prend en paramètres le buffer, le décalage dans ce +buffer et le type de donnée. Pour nous ce dernier sera `VK_INDEX_TYPE_UINT16`. + +Simplement lier le vertex buffer ne change en fait rien. Il nous faut aussi mettre à jour les commandes d'affichage +pour indiquer à Vulkan comment utiliser le buffer. Supprimez l'appel à `vkCmdDraw`, et remplacez-le par +`vkCmdDrawIndexed` : + +```c++ +vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); +``` + +Le deuxième paramètre indique le nombre d'indices. Le troisième est le nombre d'instances à invoquer (ici `1` car nous +n'utilisons par cette technique). Le paramètre suivant est un décalage dans l'index buffer, sachant qu'ici il ne +fonctionne pas en octets mais en indices. L'avant-dernier paramètre permet de fournir une valeur qui sera ajoutée à tous +les indices juste avant de les faire correspondre aux vertices. Enfin, le dernier paramètre est un décalage pour le +rendu instancié. + +Lancez le programme et vous devriez avoir ceci : + +![](/images/indexed_rectangle.png) + +Vous savez maintenant économiser la mémoire en réutilisant les vertices à l'aide d'un index buffer. Cela deviendra +crucial pour les chapitres suivants dans lesquels vous allez apprendre à charger des modèles complexes. + +Nous avons déjà évoqué le fait que le plus de buffers possibles devraient être stockés dans un seul emplacement +mémoire. Il faudrait dans l'idéal allez encore plus loin : +[les développeurs des drivers recommandent](https://developer.nvidia.com/vulkan-memory-management) également que vous +placiez plusieurs buffers dans un seul et même `VkBuffer`, et que vous utilisiez des décalages pour les différencier +dans les fonctions comme `vkCmdBindVertexBuffers`. Cela simplifie la mise des données dans des caches car elles sont +regroupées en un bloc. Il devient même possible d'utiliser la même mémoire pour plusieurs ressources si elles ne sont +pas utilisées en même temps et si elles sont proprement mises à jour. Cette pratique s'appelle d'ailleurs *aliasing*, et +certaines fonctions Vulkan possèdent un paramètre qui permet au développeur d'indiquer s'il veut utiliser la technique. + +[Code C++](/code/20_index_buffer.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git a/fr/05_Uniform_buffers/00_Descriptor_layout_et_buffer.md b/fr/05_Uniform_buffers/00_Descriptor_layout_et_buffer.md new file mode 100644 index 00000000..0a5753b2 --- /dev/null +++ b/fr/05_Uniform_buffers/00_Descriptor_layout_et_buffer.md @@ -0,0 +1,396 @@ +## Introduction + +Nous pouvons maintenant passer des données à chaque groupe d'invocation de vertex shaders. Mais qu'en est-il des +variables globales? Nous allons enfin passer à la 3D, et nous avons besoin d'une matrice model-view-projection. Nous +pourrions la transmettre avec les vertices, mais cela serait un gachis de mémoire et, de plus, nous devrions mettre à +jour le vertex buffer à chaque frame, alors qu'il est très bien rangé dans se mémoire à hautes performances. + +La solution fournie par Vulkan consiste à utiliser des *descripteurs de ressource* (ou *resource descriptors*), qui +font correspondre des données en mémoire à une variable shader. Un descripteur permet à des shaders d'accéder +librement à des ressources telles que les buffers ou les *images*. Attention, Vulkan donne un sens particulier au +terme image. Nous verrons cela bientôt. Nous allons pour l'instant créer un buffer qui contiendra les matrices de +transformation. Nous ferons en sorte que le vertex shader puisse y accéder. Il y a trois parties à l'utilisation d'un +descripteur de ressources : + +* Spécifier l'organisation des descripteurs durant la création de la pipeline +* Allouer un set de descripteurs depuis une pool de descripteurs (encore un objet de gestion de mémoire) +* Lier le descripteur pour les opérations de rendu + +L'*organisation du descripteur* (descriptor layout) indique le type de ressources qui seront accédées par la +pipeline. Cela ressemble sur le principe à indiquer les attachements accédés. Un *set de descripteurs* (descriptor +set) spécifie le buffer ou l'image qui sera lié à ce descripteur, de la même manière qu'un framebuffer doit indiquer +les ressources qui le composent. + +Il existe plusieurs types de descripteurs, mais dans ce chapitre nous ne verrons que les *uniform buffer objects* (UBO). +Nous en verrons d'autres plus tard, et leur utilisation sera très similaire. Rentrons dans le vif du sujet et supposons +maintenant que nous voulons que toutes les invocations du vertex shader que nous avons codé accèdent à la structure C +suivante : + +```c++ +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +``` + +Nous devons la copier dans un `VkBuffer` pour pouvoir y accéder à l'aide d'un descripteur UBO depuis le vertex shader. +De son côté le vertex shader y fait référence ainsi : + +```glsl +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} +``` + +Nous allons mettre à jour les matrices model, view et projection à chaque frame pour que le rectangle tourne sur +lui-même et donne un effet 3D à la scène. + +## Vertex shader + +Modifiez le vertex shader pour qu'il inclue l'UBO comme dans l'exemple ci-dessous. Je pars du principe que vous +connaissez les transformations MVP. Si ce n'est pourtant pas le cas, vous pouvez vous rendre sur +[ce site](https://www.opengl-tutorial.org/fr/beginners-tutorials/tutorial-3-matrices/) déjà mentionné dans le premier chapitre. + +```glsl +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +out gl_PerVertex { + vec4 gl_Position; +}; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} +``` + +L'ordre des variables `in`, `out` et `uniform` n'a aucune importance. La directive `binding` est assez semblable à +`location` ; elle permet de fournir l'indice du binding. Nous allons l'indiquer dans l'organisation du descripteur. +Notez le changement dans la ligne calculant `gl_Position`, qui prend maintenant en compte la matrice MVP. La dernière +composante du vecteur ne sera plus à `0`, car elle sert à diviser les autres coordonnées en fonction de leur distance à +la caméra pour créer un effet de profondeur. + +## Organisation du set de descripteurs + +La prochaine étape consiste à définir l'UBO côté C++. Nous devons aussi informer Vulkan que nous voulons l'utiliser +dans le vertex shader. + +```c++ +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +``` + +Nous pouvons faire correspondre parfaitement la déclaration en C++ avec celle dans le shader grâce à GLM. De plus les +matrices sont stockées d'une manière compatible bit à bit avec l'interprétation de ces données par les shaders. Nous +pouvons ainsi utiliser `memcpy` sur une structure `UniformBufferObject` vers un `VkBuffer`. + +Nous devons fournir des informations sur chacun des descripteurs utilisés par les shaders lors de la création de la +pipeline, similairement aux entrées du vertex shader. Nous allons créer une fonction pour gérer toute cette information, +et ainsi pour créer le set de descripteurs. Elle s'appelera `createDescriptorSetLayout` et sera appelée juste avant la +finalisation de la création de la pipeline. + +```c++ +void initVulkan() { + ... + createDescriptorSetLayout(); + createGraphicsPipeline(); + ... +} + +... + +void createDescriptorSetLayout() { + +} +``` + +Chaque `binding` doit être décrit à l'aide d'une structure de type `VkDescriptorSetLayoutBinding`. + +```c++ +void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.descriptorCount = 1; +} +``` + +Les deux premiers champs permettent de fournir la valeur indiquée dans le shader avec `binding` et le type de +descripteur auquel il correspond. Il est possible que la variable côté shader soit un tableau d'UBO, et dans ce cas +il faut indiquer le nombre d'éléments qu'il contient dans le membre `descriptorCount`. Cette possibilité pourrait être +utilisée pour transmettre d'un coup toutes les transformations spécifiques aux différents éléments d'une structure +hiérarchique. Nous n'utilisons pas cette possiblité et indiquons donc `1`. + +```c++ +uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; +``` + +Nous devons aussi informer Vulkan des étapes shaders qui accèderont à cette ressource. Le champ de bits `stageFlags` +permet de combiner toutes les étapes shader concernées. Vous pouvez aussi fournir la valeur +`VK_SHADER_STAGE_ALL_GRAPHICS`. Nous mettons uniquement `VK_SHADER_STAGE_VERTEX_BIT`. + +```c++ +uboLayoutBinding.pImmutableSamplers = nullptr; // Optionnel +``` + +Le champ `pImmutableSamplers` n'a de sens que pour les descripteurs liés aux samplers d'images. Nous nous attaquerons à +ce sujet plus tard. Vous pouvez le mettre à `nullptr`. + +Tous les liens des descripteurs sont ensuite combinés en un seul objet `VkDescriptorSetLayout`. Créez pour cela un +nouveau membre donnée : + +```c++ +VkDescriptorSetLayout descriptorSetLayout; +VkPipelineLayout pipelineLayout; +``` + +Nous pouvons créer cet objet à l'aide de la fonction `vkCreateDescriptorSetLayout`. Cette fonction prend en argument une +structure de type `VkDescriptorSetLayoutCreateInfo`. Elle contient un tableau contenant les structures qui décrivent les +bindings : + +```c++ +VkDescriptorSetLayoutCreateInfo layoutInfo{}; +layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; +layoutInfo.bindingCount = 1; +layoutInfo.pBindings = &uboLayoutBinding; + +if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("echec de la creation d'un set de descripteurs!"); +} +``` + +Nous devons fournir cette structure à Vulkan durant la création de la pipeline graphique. Ils sont transmis par la +structure `VkPipelineLayoutCreateInfo`. Modifiez ainsi la création de cette structure : + +```c++ +VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; +pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +pipelineLayoutInfo.setLayoutCount = 1; +pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; +``` + +Vous vous demandez peut-être pourquoi il est possible de spécifier plusieurs set de descripteurs dans cette structure, +dans la mesure où un seul inclut tous les `bindings` d'une pipeline. Nous y reviendrons dans le chapitre suivant, quand +nous nous intéresserons aux pools de descripteurs. + +L'objet que nous avons créé ne doit être détruit que lorsque le programme se termine. + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + ... +} +``` + +## Uniform buffer + +Dans le prochain chapitre nous référencerons le buffer qui contient les données de l'UBO. Mais nous devons bien sûr +d'abord créer ce buffer. Comme nous allons accéder et modifier les données du buffer à chaque frame, il est assez +inutile d'utiliser un buffer intermédiaire. Ce serait même en fait contre-productif en terme de performances. + +Comme des frames peuvent être "in flight" pendant que nous essayons de modifier le contenu du buffer, nous allons avoir +besoin de plusieurs buffers. Nous pouvons soit en avoir un par frame, soit un par image de la swap chain. Comme nous +avons un command buffer par image nous allons utiliser cette seconde méthode. + +Pour cela créez les membres données `uniformBuffers` et `uniformBuffersMemory` : + +```c++ +VkBuffer indexBuffer; +VkDeviceMemory indexBufferMemory; + +std::vector uniformBuffers; +std::vector uniformBuffersMemory; +``` + +Créez ensuite une nouvelle fonction appelée `createUniformBuffers` et appelez-la après `createIndexBuffers`. Elle +allouera les buffers : + +```c++ +void initVulkan() { + ... + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + ... +} + +... + +void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } +} +``` + +Nous allons créer une autre fonction qui mettra à jour le buffer en appliquant à son contenu une transformation à chaque +frame. Nous n'utiliserons donc pas `vkMapMemory` ici. Le buffer doit être détruit à la fin du programme. Mais comme il +dépend du nombre d'images de la swap chain, et que ce nombre peut évoluer lors d'une reécration, nous devons le +supprimer depuis `cleanupSwapChain` : + +```c++ +void cleanupSwapChain() { + ... + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + ... +} +``` + +Nous devons également le recréer depuis `recreateSwapChain` : + +```c++ +void recreateSwapChain() { + ... + createFramebuffers(); + createUniformBuffers(); + createCommandBuffers(); +} +``` + +## Mise à jour des données uniformes + +Créez la fonction `updateUniformBuffer` et appelez-la dans `drawFrame`, juste après que nous avons déterminé l'image de +la swap chain que nous devons acquérir : + +```c++ +void drawFrame() { + ... + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + ... + + updateUniformBuffer(imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + ... +} + +... + +void updateUniformBuffer(uint32_t currentImage) { + +} +``` + +Cette fonction générera une rotation à chaque frame pour que la géométrie tourne sur elle-même. Pour ces fonctionnalités +mathématiques nous devons inclure deux en-têtes : + +```c++ +#define GLM_FORCE_RADIANS +#include +#include + +#include +``` + +Le header `` expose des fonctions comme `glm::rotate`, `glm:lookAt` ou `glm::perspective`, +dont nous avons besoin pour implémenter la 3D. La macro `GLM_FORCE_RADIANS` permet d'éviter toute confusion sur la +représentation des angles. + +Pour que la rotation s'exécute à une vitesse indépendante du FPS, nous allons utiliser les fonctionnalités de mesure +précise de la librairie standrarde C++. Incluez donc `` : + +```c++ +void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); +} +``` + +Nous commençons donc par écrire la logique de calcul du temps écoulé, mesuré en secondes et stocké dans un `float`. + +Nous allons ensuite définir les matrices model, view et projection stockées dans l'UBO. La rotation sera implémentée +comme une simple rotation autour de l'axe Z en fonction de la variable `time` : + +```c++ +UniformBufferObject ubo{}; +ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); +``` + +La fonction `glm::rotate` accepte en argument une matrice déjà existante, un angle de rotation et un axe de rotation. Le +constructeur `glm::mat4(1.0)` crée une matrice identité. Avec la multiplication `time * glm::radians(90.0f)` la +géométrie tournera de 90 degrés par seconde. + +```c++ +ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); +``` + +Pour la matrice view, j'ai décidé de la générer de telle sorte que nous regardions le rectangle par dessus avec une +inclinaison de 45 degrés. La fonction `glm::lookAt` prend en arguments la position de l'oeil, la cible du regard et +l'axe servant de référence pour le haut. + +```c++ +ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); +``` + +J'ai opté pour un champ de vision de 45 degrés. Les autres paramètres de `glm::perspective` sont le ratio et les plans +near et far. Il est important d'utiliser l'étendue actuelle de la swap chain pour calculer le ratio, afin d'utiliser les +valeurs qui prennent en compte les redimensionnements de la fenêtre. + +```c++ +ubo.proj[1][1] *= -1; +``` + +GLM a été conçue pour OpenGL, qui utilise les coordonnées de clip et de l'axe Y à l'envers. La manière la plus simple de +compenser cela consiste à changer le signe de l'axe Y dans la matrice de projection. + +Maintenant que toutes les transformations sont définies nous pouvons copier les données dans le buffer uniform actuel. +Nous utilisons la première technique que nous avons vue pour la copie de données dans un buffer. + +```c++ +void* data; +vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); + memcpy(data, &ubo, sizeof(ubo)); +vkUnmapMemory(device, uniformBuffersMemory[currentImage]); +``` + +Utiliser un UBO de cette manière n'est pas le plus efficace pour transmettre des données fréquemment mises à jour. Une +meilleure pratique consiste à utiliser les *push constants*, que nous aborderons peut-être dans un futur chapitre. + +Dans un avenir plus proche nous allons lier les sets de descripteurs au `VkBuffer` contenant les données des matrices, +afin que le vertex shader puisse y avoir accès. + +[Code C++](/code/21_descriptor_layout.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git a/fr/05_Uniform_buffers/01_Descriptor_pool_et_sets.md b/fr/05_Uniform_buffers/01_Descriptor_pool_et_sets.md new file mode 100644 index 00000000..d4763b92 --- /dev/null +++ b/fr/05_Uniform_buffers/01_Descriptor_pool_et_sets.md @@ -0,0 +1,399 @@ +## Introduction + +L'objet `VkDescriptorSetLayout` que nous avons créé dans le chapitre précédent décrit les descripteurs que nous devons +lier pour les opérations de rendu. Dans ce chapitre nous allons créer les véritables sets de descripteurs, un pour +chaque `VkBuffer`, afin que nous puissions chacun les lier au descripteur de l'UBO côté shader. + +## Pool de descripteurs + +Les sets de descripteurs ne peuvent pas être crées directement. Il faut les allouer depuis une pool, comme les command +buffers. Nous allons créer la fonction `createDescriptorPool` pour générer une pool de descripteurs. + +```c++ +void initVulkan() { + ... + createUniformBuffer(); + createDescriptorPool(); + ... +} + +... + +void createDescriptorPool() { + +} +``` + +Nous devons d'abord indiquer les types de descripteurs et combien sont compris dans les sets. Nous utilisons pour cela +une structure du type `VkDescriptorPoolSize` : + +```c++ +VkDescriptorPoolSize poolSize{}; +poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +poolSize.descriptorCount = static_cast(swapChainImages.size()); +``` + +Nous allons allouer un descripteur par frame. Cette structure doit maintenant être référencée dans la structure +principale `VkDescriptorPoolCreateInfo`. + +```c++ +VkDescriptorPoolCreateInfo poolInfo{}; +poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; +poolInfo.poolSizeCount = 1; +poolInfo.pPoolSizes = &poolSize; +``` + +Nous devons aussi spécifier le nombre maximum de sets de descripteurs que nous sommes susceptibles d'allouer. + +```c++ +poolInfo.maxSets = static_cast(swapChainImages.size()); +``` + +La structure possède un membre optionnel également présent pour les command pools. Il permet d'indiquer que les +sets peuvent être libérés indépendemment les uns des autres avec la valeur +`VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT`. Comme nous n'allons pas toucher aux descripteurs pendant que le +programme s'exécute, nous n'avons pas besoin de l'utiliser. Indiquez `0` pour ce champ. + +```c++ +VkDescriptorPool descriptorPool; + +... + +if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("echec de la creation de la pool de descripteurs!"); +} +``` + +Créez un nouveau membre donnée pour référencer la pool, puis appelez `vkCreateDescriptorPool`. La pool doit être +recrée avec la swap chain.. + +```c++ +void cleanupSwapChain() { + ... + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + ... +} +``` + +Et recréée dans `recreateSwapChain` : + +```c++ +void recreateSwapChain() { + ... + createUniformBuffers(); + createDescriptorPool(); + createCommandBuffers(); +} +``` + +## Set de descripteurs + +Nous pouvons maintenant allouer les sets de descripteurs. Créez pour cela la fonction `createDescriptorSets` : + +```c++ +void initVulkan() { + ... + createDescriptorPool(); + createDescriptorSets(); + ... +} + +... + +void createDescriptorSets() { + +} +``` + +L'allocation de cette ressource passe par la création d'une structure de type `VkDescriptorSetAllocateInfo`. Vous devez +bien sûr y indiquer la pool d'où les allouer, de même que le nombre de sets à créer et l'organisation qu'ils doivent +suivre. + +```c++ +std::vector layouts(swapChainImages.size(), descriptorSetLayout); +VkDescriptorSetAllocateInfo allocInfo{}; +allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; +allocInfo.descriptorPool = descriptorPool; +allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); +allocInfo.pSetLayouts = layouts.data(); +``` + +Dans notre cas nous allons créer autant de sets qu'il y a d'images dans la swap chain. Ils auront tous la même +organisation. Malheureusement nous devons copier la structure plusieurs fois car la fonction que nous allons utiliser +prend en argument un tableau, dont le contenu doit correspondre indice à indice aux objets à créer. + +Ajoutez un membre donnée pour garder une référence aux sets, et allouez-les avec `vkAllocateDescriptorSets` : + +```c++ +VkDescriptorPool descriptorPool; +std::vector descriptorSets; + +... + +descriptorSets.resize(swapChainImages.size()); +if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("echec de l'allocation d'un set de descripteurs!"); +} +``` + +Il n'est pas nécessaire de détruire les sets de descripteurs explicitement, car leur libération est induite par la +destruction de la pool. L'appel à `vkAllocateDescriptorSets` alloue donc tous les sets, chacun possédant un unique +descripteur d'UBO. + +```c++ +void cleanup() { + ... + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + ... +} +``` + +Nous avons créé les sets mais nous n'avons pas paramétré les descripteurs. Nous allons maintenant créer une boucle pour +rectifier ce problème : + +```c++ +for (size_t i = 0; i < swapChainImages.size(); i++) { + +} +``` + +Les descripteurs référant à un buffer doivent être configurés avec une structure de type `VkDescriptorBufferInfo`. Elle +indique le buffer contenant les données, et où les données y sont stockées. + +```c++ +for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); +} +``` + +Nous allons utiliser tout le buffer, il est donc aussi possible d'indiquer `VK_WHOLE_SIZE`. La configuration des +descripteurs est maintenant mise à jour avec la fonction `vkUpdateDescriptorSets`. Elle prend un tableau de +`VkWriteDescriptorSet` en paramètre. + +```c++ +VkWriteDescriptorSet descriptorWrite{}; +descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +descriptorWrite.dstSet = descriptorSets[i]; +descriptorWrite.dstBinding = 0; +descriptorWrite.dstArrayElement = 0; +``` + +Les deux premiers champs spécifient le set à mettre à jour et l'indice du binding auquel il correspond. Nous avons donné +à notre unique descripteur l'indice `0`. Souvenez-vous que les descripteurs peuvent être des tableaux ; nous devons donc +aussi indiquer le premier élément du tableau que nous voulons modifier. Nous n'en n'avons qu'un. + +```c++ +descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +descriptorWrite.descriptorCount = 1; +``` + +Nous devons encore indiquer le type du descripteur. Il est possible de mettre à jour plusieurs descripteurs d'un même +type en même temps. La fonction commence à `dstArrayElement` et s'étend sur `descriptorCount` descripteurs. + +```c++ +descriptorWrite.pBufferInfo = &bufferInfo; +descriptorWrite.pImageInfo = nullptr; // Optionnel +descriptorWrite.pTexelBufferView = nullptr; // Optionnel +``` + +Le dernier champ que nous allons utiliser est `pBufferInfo`. Il permet de fournir `descriptorCount` structures qui +configureront les descripteurs. Les autres champs correspondent aux structures qui peuvent configurer des descripteurs +d'autres types. Ainsi il y aura `pImageInfo` pour les descripteurs liés aux images, et `pTexelBufferInfo` pour les +descripteurs liés aux buffer views. + +```c++ +vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); +``` + +Les mises à jour sont appliquées quand nous appelons `vkUpdateDescriptorSets`. La fonction accepte deux tableaux, un de +`VkWriteDesciptorSets` et un de `VkCopyDescriptorSet`. Le second permet de copier des descripteurs. + +## Utiliser des sets de descripteurs + +Nous devons maintenant étendre `createCommandBuffers` pour qu'elle lie les sets de descripteurs aux descripteurs des +shaders avec la commande `vkCmdBindDescriptorSets`. Il faut invoquer cette commande dans l'enregistrement des command +buffers avant l'appel à `vkCmdDrawIndexed`. + +```c++ +vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr); +vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); +``` + +Au contraire des buffers de vertices et d'indices, les sets de descripteurs ne sont pas spécifiques aux pipelines +graphiques. Nous devons donc spécifier que nous travaillons sur une pipeline graphique et non pas une pipeline de +calcul. Le troisième paramètre correspond à l'organisation des descripteurs. Viennent ensuite l'indice du premier +descripteur, la quantité à évaluer et bien sûr le set d'où ils proviennent. Nous y reviendrons. Les deux derniers +paramètres sont des décalages utilisés pour les descripteurs dynamiques. Nous y reviendrons aussi dans un futur +chapitre. + +Si vous lanciez le programme vous verrez que rien ne s'affiche. Le problème est que l'inversion de la coordonnée Y dans +la matrice induit l'évaluation des vertices dans le sens inverse des aiguilles d'une montre (*counter-clockwise* en anglais), +alors que nous voudrions le contraire. En effet, les systèmes actuels utilisent ce sens de rotation pour détermnier la face de devant. +La face de derrière est ensuite simplement ignorée. C'est pourquoi notre géométrie n'est pas rendue. C'est le *backface culling*. +Changez le champ `frontface` de la structure `VkPipelineRasterizationStateCreateInfo` dans la fonction +`createGraphicsPipeline` de la manière suivante : + +```c++ +rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; +rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; +``` + +Maintenant vous devriez voir ceci en lançant votre programme : + +![](/images/spinning_quad.png) + +Le rectangle est maintenant un carré car la matrice de projection corrige son aspect. La fonction `updateUniformBuffer` +inclut d'office les redimensionnements d'écran, il n'est donc pas nécessaire de recréer les descripteurs dans +`recreateSwapChain`. + +## Alignement + +Jusqu'à présent nous avons évité la question de la compatibilité des types côté C++ avec la définition des types pour +les variables uniformes. Il semble évident d'utiliser des types au même nom des deux côtés : + +```c++ +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; +``` + +Pourtant ce n'est pas aussi simple. Essayez la modification suivante : + +```c++ +struct UniformBufferObject { + glm::vec2 foo; + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +layout(binding = 0) uniform UniformBufferObject { + vec2 foo; + mat4 model; + mat4 view; + mat4 proj; +} ubo; +``` + +Recompilez les shaders et relancez le programme. Le carré coloré a disparu! La raison réside dans cette question de +l'alignement. + +Vulkan s'attend à un certain alignement des données en mémoire pour chaque type. Par exemple : + +* Les scalaires doivent être alignés sur leur nombre d'octets N (float de 32 bits donne un alognement de 4 octets) +* Un `vec2` doit être aligné sur 2N (8 octets) +* Les `vec3` et `vec4` doivent être alignés sur 4N (16 octets) +* Une structure imbriquée doit être alignée sur la somme des alignements de ses membres arrondie sur le multiple de +16 octets au-dessus +* Une `mat4` doit avoir le même alignement qu'un `vec4` + +Les alignemenents imposés peuvent être trouvés dans +[la spécification](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap15.html#interfaces-resources-layout) + +Notre shader original et ses trois `mat4` était bien aligné. `model` a un décalage de 0, `view` de 64 et `proj` de 128, +ce qui sont des multiples de 16. + +La nouvelle structure commence avec un membre de 8 octets, ce qui décale tout ce qui suit. Les décalages sont augmentés +de 8 et ne sont alors plus multiples de 16. Nous pouvons fixer ce problème avec le mot-clef `alignas` : + +```c++ +struct UniformBufferObject { + glm::vec2 foo; + alignas(16) glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +``` + +Si vous recompilez et relancez, le programme devrait fonctionner à nouveau. + +Heureusement pour nous, GLM inclue un moyen qui nous permet de plus penser à ce souci d'alignement : + +```c++ +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES +#include +``` + +La ligne `#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES` force GLM a s'assurer de l'alignement des types qu'elle expose. +La limite de cette méthode s'atteint en utilisant des structures imbriquées. Prenons l'exemple suivant : + +```c++ +struct Foo { + glm::vec2 v; +}; +struct UniformBufferObject { + Foo f1; + Foo f2; +}; +``` + +Et côté shader mettons : + +```c++ +struct Foo { + vec2 v; +}; +layout(binding = 0) uniform UniformBufferObject { + Foo f1; + Foo f2; +} ubo; +``` + +Nous nous retrouvons avec un décalage de 8 pour `f2` alors qu'il lui faudrait un décalage de 16. Il faut dans ce cas +de figure utiliser `alignas` : + +```c++ +struct UniformBufferObject { + Foo f1; + alignas(16) Foo f2; +}; +``` + +Pour cette raison il est préférable de toujours être explicite à propos de l'alignement de données que l'on envoie aux +shaders. Vous ne serez pas supris par des problèmes d'alignement imprévus. + +```c++ +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; +``` + +Recompilez le shader avant de continuer la lecture. + +## Plusieurs sets de descripteurs + +Comme on a pu le voir dans les en-têtes de certaines fonctions, il est possible de lier plusieurs sets de descripteurs +en même temps. Vous devez fournir une organisation pour chacun des sets pendant la mise en place de l'organisation de la +pipeline. Les shaders peuvent alors accéder aux descripteurs de la manière suivante : + +```c++ +layout(set = 0, binding = 0) uniform UniformBufferObject { ... } +``` + +Vous pouvez utiliser cette possibilité pour placer dans différents sets les descripteurs dépendant d'objets et les +descripteurs partagés. De cette manière vous éviter de relier constemment une partie des descripteurs, ce qui peut être +plus performant. + +[Code C++](/code/22_descriptor_sets.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git a/fr/06_Texture_mapping/00_Images.md b/fr/06_Texture_mapping/00_Images.md new file mode 100644 index 00000000..32e8ec63 --- /dev/null +++ b/fr/06_Texture_mapping/00_Images.md @@ -0,0 +1,687 @@ +## Introduction + +Jusqu'à présent nous avons écrit les couleurs dans les données de chaque sommet, pratique peu efficace. Nous allons +maintenant implémenter l'échantillonnage (sampling) des textures, afin que le rendu soit plus intéressant. Nous +pourrons ensuite passer à l'affichage de modèles 3D dans de futurs chapitres. + +L'ajout d'une texture comprend les étapes suivantes : + +* Créer un objet *image* stocké sur la mémoire de la carte graphique +* La remplir avec les pixels extraits d'un fichier image +* Créer un sampler +* Ajouter un descripteur pour l'échantillonnage de l'image + +Nous avons déjà travaillé avec des images, mais nous n'en avons jamais créé. Celles que nous avons manipulées avaient +été automatiquement crées par la swap chain. Créer une image et la remplir de pixels ressemble à la création d'un vertex +buffer. Nous allons donc commencer par créer une ressource intermédiaire pour y faire transiter les données que nous +voulons retrouver dans l'image. Bien qu'il soit possible d'utiliser une image comme intermédiaire, il est aussi autorisé +de créer un `VkBuffer` comme intermédiaire vers l'image, et cette méthode est +[plus rapide sur certaines plateformes](https://developer.nvidia.com/vulkan-memory-management). Nous allons donc +d'abord créer un buffer et y mettre les données relatives aux pixels. Pour l'image nous devrons nous enquérir des +spécificités de la mémoire, allouer la mémoire nécessaire et y copier les pixels. Cette procédure est très +similaire à la création de buffers. + +La grande différence - il en fallait une tout de même - réside dans l'organisation des données à l'intérieur même des +pixels. Leur organisation affecte la manière dont les données brutes de la mémoire sont interprétées. De plus, stocker +les pixels ligne par ligne n'est pas forcément ce qui se fait de plus efficace, et cela est dû à la manière dont les +cartes graphiques fonctionnent. Nous devrons donc faire en sorte que les images soient organisées de la meilleure +manière possible. Nous avons déjà croisé certaines organisation lors de la création de la passe de rendu : + +* `VK_IMAGE_LAYOUT_PRESENT_SCR_KHR` : optimal pour la présentation +* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` : optimal pour être l'attachement cible du fragment shader donc en tant que +cible de rendu +* `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` : optimal pour être la source d'un transfert comme `vkCmdCopyImageToBuffer` +* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` : optimal pour être la cible d'un transfert comme `vkCmdCopyBufferToImage` +* `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` : optimal pour être échantillonné depuis un shader + +La plus commune des méthode spour réaliser une transition entre différentes organisations est la *barrière pipeline*. +Celles-ci sont principalement utilisées pour synchroniser l'accès à une ressource, mais peuvent aussi permettre la +transition d'un état à un autre. Dans ce chapitre nous allons utiliser cette seconde possibilité. Les barrières peuvent +enfin être utilisées pour changer la queue family qui possède une ressource. + +## Librairie de chargement d'image + +De nombreuses librairies de chargement d'images existent ; vous pouvez même écrire la vôtre pour des formats simples +comme BMP ou PPM. Nous allons utiliser stb_image, de [la collection stb](https://github.com/nothings/stb). Elle +possède l'avantage d'être écrite en un seul fichier. Téléchargez donc `stb_image.h` et placez-la ou vous voulez, par +exemple dans le dossier où sont stockés GLFW et GLM. + +**Visual Studio** + +Ajoutez le dossier comprenant `stb_image.h` dans `Additional Include Directories`. + +![](/images/include_dirs_stb.png) + +**Makefile** + +Ajoutez le dossier comprenant `stb_image.h` aux chemins parcourus par GCC : + +```text +VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64 +STB_INCLUDE_PATH = /home/user/libraries/stb + +... + +CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) +``` + +## Charger une image + +Incluez la librairie de cette manière : + +```c++ +#define STB_IMAGE_IMPLEMENTATION +#include +``` + +Le header simple ne fournit que les prototypes des fonctions. Nous devons demander les implémentations avec la define +`STB_IMAGE_IMPLEMENTATION` pour ne pas avoir d'erreurs à l'édition des liens. + +```c++ +void initVulkan() { + ... + createCommandPool(); + createTextureImage(); + createVertexBuffer(); + ... +} + +... + +void createTextureImage() { + +} +``` + +Créez la fonction `createTextureImage`, depuis laquelle nous chargerons une image et la placerons dans un objet Vulkan +représentant une image. Nous allons avoir besoin de command buffers, il faut donc appeler cette fonction après +`createCommandPool`. + +Créez un dossier `textures` au même endroit que `shaders` pour y placer les textures. Nous allons y mettre un fichier +appelé `texture.jpg` pour l'utiliser dans notre programme. J'ai choisi d'utiliser +[cette image de license CC0](https://pixbay.com/en/statue-sculpture-fig-historically-1275469) redimensionnée à 512x512, +mais vous pouvez bien sûr en utiliser une autre. La librairie supporte des formats tels que JPEG, PNG, BMP ou GIF. + +![](/images/texture.jpg) + +Le chargement d'une image est très facile avec cette librairie : + +```c++ +void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("échec du chargement d'une image!"); + } +} +``` + +La fonction `stbi_load` prend en argument le chemin de l'image et les différentes canaux à charger. L'argument +`STBI_rgb_alpha` force la fonction à créer un canal alpha même si l'image originale n'en possède pas. Cela simplifie le +travail en homogénéisant les situations. Les trois arguments transmis en addresse servent de résultats pour stocker +des informations sur l'image. Les pixels sont retournés sous forme du pointeur `stbi_uc *pixels`. Ils sont organisés +ligne par ligne et ont chacun 4 octets, ce qui représente `texWidth * texHeight * 4` octets au total pour l'image. + +## Buffer intermédiaire + +Nous allons maintenant créer un buffer en mémoire accessible pour que nous puissions utiliser `vkMapMemory` et y placer +les pixels. Ajoutez les variables suivantes à la fonction pour contenir ce buffer temporaire : + +```c++ +VkBuffer stagingBuffer; +VkDeviceMemory stagingBufferMemory; +``` + +Le buffer doit être en mémoire visible pour que nous puissions le mapper, et il doit être utilisable comme source d'un +transfert vers une image, d'où l'appel suivant : + +```c++ +createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); +``` + +Nous pouvons placer tel quels les pixels que nous avons récupérés dans le buffer : + +```c++ +void* data; +vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); +vkUnmapMemory(device, stagingBufferMemory); +``` + +Il ne faut surtout pas oublier de libérer le tableau de pixels après cette opération : + +```c++ +stbi_image_free(pixels); +``` + +## Texture d'image + +Bien qu'il nous soit possible de paramétrer le shader afin qu'il utilise le buffer comme source de pixels, il est bien +plus efficace d'utiliser un objet image. Ils rendent plus pratique, mais surtout plus rapide, l'accès aux données de +l'image en nous permettant d'utiliser des coordonnées 2D. Les pixels sont appelés texels dans le contexte du shading, et +nous utiliserons ce terme à partir de maintenant. Ajoutez les membres données suivants : + +```c++ +VkImage textureImage; +VkDeviceMemory textureImageMemory; +``` + +Les paramètres pour la création d'une image sont indiqués dans une structure de type `VkImageCreateInfo` : + +```c++ +VkImageCreateInfo imageInfo{}; +imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; +imageInfo.imageType = VK_IMAGE_TYPE_2D; +imageInfo.extent.width = static_cast(texWidth); +imageInfo.extent.height = static_cast(texHeight); +imageInfo.extent.depth = 1; +imageInfo.mipLevels = 1; +imageInfo.arrayLayers = 1; +``` + +Le type d'image contenu dans `imageType` indique à Vulkan le repère dans lesquels les texels sont placés. Il est +possible de créer des repères 1D, 2D et 3D. Les images 1D peuvent être utilisés comme des tableaux ou des gradients. Les +images 2D sont majoritairement utilisés comme textures. Certaines techniques les utilisent pour stocker autre chose +que des couleur, par exemple des vecteurs. Les images 3D peuvent être utilisées pour stocker des voxels par +exemple. Le champ `extent` indique la taille de l'image, en terme de texels par axe. Comme notre texture fonctionne +comme un plan dans un espace en 3D, nous devons indiquer `1` au champ `depth`. Finalement, notre texture n'est pas un +tableau, et nous verrons le mipmapping plus tard. + +```c++ +imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB; +``` + +Vulkan supporte de nombreux formats, mais nous devons utiliser le même format que les données présentes dans le buffer. + +```c++ +imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; +``` + +Le champ `tiling` peut prendre deux valeurs : + +* `VK_IMAGE_TILING_LINEAR` : les texels sont organisés ligne par ligne +* `VK_IMAGE_TILING_OPTIMAL` : les texels sont organisés de la manière la plus optimale pour l'implémentation + +Le mode mis dans `tiling` ne peut pas être changé, au contraire de l'organisation de l'image. Par conséquent, si vous +voulez pouvoir directement accéder aux texels, comme il faut qu'il soient organisés d'une manière logique, il vous faut +indiquer `VK_IMAGE_TILING_LINEAR`. Comme nous utilisons un buffer intermédiaire et non une image intermédiaire, nous +pouvons utiliser le mode le plus efficace. + +```c++ +imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +``` + +Idem, il n'existe que deux valeurs pour `initialLayout` : + +* `VK_IMAGE_LAYOUT_UNDEFINED` : inutilisable par le GPU, son contenu sera éliminé à la première transition +* `VK_IMAGE_LAYOUT_PREINITIALIZED` : inutilisable par le GPU, mais la première transition conservera les texels + +Il n'existe que quelques situations où il est nécessaire de préserver les texels pendant la première transition. L'une +d'elle consiste à utiliser l'image comme ressource intermédiaire en combinaison avec `VK_IMAGE_TILING_LINEAR`. Il +faudrait dans ce cas la faire transitionner vers un état source de transfert, sans perte de données. Cependant nous +utilisons un buffer comme ressource intermédiaire, et l'image transitionne d'abord vers cible de transfert. À ce +moment-là elle n'a pas de donnée intéressante. + +```c++ +imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; +``` + +Le champ de bits `usage` fonctionne de la même manière que pour la création des buffers. L'image sera destination +d'un transfert, et sera utilisée par les shaders, d'où les deux indications ci-dessus. + +```c++ +imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; +``` + +L'image ne sera utilisée que par une famille de queues : celle des graphismes (qui rappelons-le supporte +implicitement les transferts). Si vous avez choisi d'utiliser une queue spécifique vous devrez mettre +`VK_SHARING_MODE_CONCURENT`. + +```c++ +imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; +imageInfo.flags = 0; // Optionnel +``` + +Le membre `sample` se réfère au multisampling. Il n'a de sens que pour les images utilisées comme attachements d'un +framebuffer, nous devons donc mettre `1`, traduit par `VK_SAMPLE_COUNT_1_BIT`. Finalement, certaines informations se +réfèrent aux *images étendues*. Ces image étendues sont des images dont seule une partie est stockée dans la mémoire. +Voici une exemple d'utilisation : si vous utilisiez une image 3D pour représenter un terrain à l'aide de voxels, vous +pourriez utiliser cette fonctionnalité pour éviter d'utiliser de la mémoire qui au final ne contiendrait que de l'air. +Nous ne verrons pas cette fonctionnalité dans ce tutoriel, donnez à `flags` la valeur `0`. + +```c++ +if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) { + throw std::runtime_error("echec de la creation d'une image!"); +} +``` + +L'image est créée par la fonction `vkCreateImage`, qui ne possède pas d'argument particulièrement intéressant. Il est +possible que le format `VK_FORMAT_R8G8B8A8_SRGB` ne soit pas supporté par la carte graphique, mais c'est tellement peu +probable que nous ne verrons pas comment y remédier. En effet utiliser un autre format demanderait de réaliser plusieurs +conversions compliquées. Nous reviendrons sur ces conversions dans le chapitre sur le buffer de profondeur. + +```c++ +VkMemoryRequirements memRequirements; +vkGetImageMemoryRequirements(device, textureImage, &memRequirements); + +VkMemoryAllocateInfo allocInfo{}; +allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; +allocInfo.allocationSize = memRequirements.size; +allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + +if (vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory) != VK_SUCCESS) { + throw std::runtime_error("echec de l'allocation de la mémoire pour l'image!"); +} + +vkBindImageMemory(device, textureImage, textureImageMemory, 0); +``` + +L'allocation de la mémoire nécessaire à une image fonctionne également de la même façon que pour un buffer. Seuls les +noms de deux fonctions changent : `vkGetBufferMemoryRequirements` devient `vkGetImageMemoryRequirements` et +`vkBindBufferMemory` devient `vkBindImageMemory`. + +Cette fonction est déjà assez grande ainsi, et comme nous aurons besoin d'autres images dans de futurs chapitres, il est +judicieux de déplacer la logique de leur création dans une fonction, comme nous l'avons fait pour les buffers. Voici +donc la fonction `createImage` : + +```c++ +void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("echec de la creation d'une image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("echec de l'allocation de la memoire d'une image!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); +} +``` + +La largeur, la hauteur, le mode de tiling, l'usage et les propriétés de la mémoire sont des paramètres car ils varierons +toujours entre les différentes images que nous créerons dans ce tutoriel. + +La fonction `createTextureImage` peut maintenant être réduite à ceci : + +```c++ +void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("échec du chargement de l'image!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +} +``` + +## Transitions de l'organisation + +La fonction que nous allons écrire inclut l'enregistrement et l'exécution de command buffers. Il est donc également +judicieux de placer cette logique dans une autre fonction : + +```c++ +VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; +} + +void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); +} +``` + +Le code de ces fonctions est basé sur celui de `copyBuffer`. Vous pouvez maintenant réduire `copyBuffer` à : + +```c++ +void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); +} +``` + +Si nous utilisions de simples buffers nous pourrions nous contenter d'écrire une fonction qui enregistre l'appel à +`vkCmdCopyBufferToImage`. Mais comme cette fonction utilse une image comme cible nous devons changer l'organisation de +l'image avant l'appel. Créez une nouvelle fonction pour gérer de manière générique les transitions : + +```c++ +void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + endSingleTimeCommands(commandBuffer); +} +``` + +L'une des manières de réaliser une transition consiste à utiliser une *barrière pour mémoire d'image*. Une telle barrière +de pipeline est en général utilisée pour synchroniser l'accès à une ressource, mais nous avons déjà évoqué ce sujet. Il +existe au passage un équivalent pour les buffers : une barrière pour mémoire de buffer. + +```c++ +VkImageMemoryBarrier barrier{}; +barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; +barrier.oldLayout = oldLayout; +barrier.newLayout = newLayout; +``` + +Les deux premiers champs indiquent la transition à réaliser. Il est possible d'utiliser `VK_IMAGE_LAYOUT_UNDEFINED` pour +`oldLayout` si le contenu de l'image ne vous intéresse pas. + +```c++ +barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; +barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; +``` + +Ces deux paramètres sont utilisés pour transmettre la possession d'une queue à une autre. Il faut leur indiquer les +indices des familles de queues correspondantes. Comme nous ne les utilisons pas, nous devons les mettre à +`VK_QUEUE_FAMILY_IGNORED`. + +```c++ +barrier.image = image; +barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +barrier.subresourceRange.baseMipLevel = 0; +barrier.subresourceRange.levelCount = 1; +barrier.subresourceRange.baseArrayLayer = 0; +barrier.subresourceRange.layerCount = 1; +``` + +Les paramètres `image` et `subresourceRange` servent à indiquer l'image, puis la partie de l'image concernées par les +changements. Comme notre image n'est pas un tableau, et que nous n'avons pas mis en place de mipmapping, les +paramètres sont tous mis au minimum. + +```c++ +barrier.srcAccessMask = 0; // TODO +barrier.dstAccessMask = 0; // TODO +``` + +Comme les barrières sont avant tout des objets de synchronisation, nous devons indiquer les opérations utilisant la +ressource avant et après l'exécution de cette barrière. Pour pouvoir remplir les champs ci-dessus nous devons +déterminer ces opérations, ce que nous ferons plus tard. + +```c++ +vkCmdPipelineBarrier( + commandBuffer, + 0 /* TODO */, 0 /* TODO */, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier +); +``` + +Tous les types de barrière sont mis en place à l'aide de la même fonction. Le paramètre qui suit le command buffer +indique une étape de la pipeline. Durant celle-ci seront réalisées les opération devant précéder la barrière. Le +paramètre d'après indique également une étape de la pipeline. Cette fois les opérations exécutées durant cette étape +attendront la barrière. Les étapes que vous pouvez fournir comme avant- et après-barrière dépendent de l'utilisation +des ressources qui y sont utilisées. Les valeurs autorisées sont listées +[dans ce tableau](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-access-types-supported). +Par exemple, si vous voulez lire des données présentes dans un UBO après une barrière qui s'applique au buffer, vous +devrez indiquer `VK_ACCESS_UNIFORM_READ_BIT` comme usage, et si le premier shader à utiliser l'uniform est le fragment +shader il vous faudra indiquer `VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT` comme étape. Dans ce cas de figure, spécifier +une autre étape qu'une étape shader n'aurait aucun sens, et les validation layers vous le feraient remarquer. + +Le paramètre sur la troisième ligne peut être soit `0` soit `VK_DEPENDENCY_BY_REGION_BIT`. Dans ce second cas la +barrière devient une condition spécifique d'une région de la ressource. Cela signifie entre autres que l'implémentation +peut lire une région aussitôt que le transfert y est terminé, sans considération pour les autres régions. Cela permet +d'augmenter encore les performances en permettant d'utiliser les optimisations des architectures actuelles. + +Les trois dernières paires de paramètres sont des tableaux de barrières pour chacun des trois types existants : barrière +mémorielle, barrière de buffer et barrière d'image. + +## Copier un buffer dans une image + +Avant de compléter `vkCreateTextureImage` nous allons écrire une dernière fonction appelée `copyBufferToImage` : + +```c++ +void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + endSingleTimeCommands(commandBuffer); +} +``` + +Comme avec les recopies de buffers, nous devons indiquer les parties du buffer à copier et les parties de l'image où +écrire. Ces données doivent être placées dans une structure de type `VkBufferImageCopy`. + +```c++ +VkBufferImageCopy region{}; +region.bufferOffset = 0; +region.bufferRowLength = 0; +region.bufferImageHeight = 0; + +region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +region.imageSubresource.mipLevel = 0; +region.imageSubresource.baseArrayLayer = 0; +region.imageSubresource.layerCount = 1; + +region.imageOffset = {0, 0, 0}; +region.imageExtent = { + width, + height, + 1 +}; +``` + +La plupart de ces champs sont évidents. `bufferOffset` indique l'octet à partir duquel les données des pixels commencent +dans le buffer. L'organisation des pixels doit être indiquée dans les champs `bufferRowLenght` et `bufferImageHeight`. +Il pourrait en effet avoir un espace entre les lignes de l'image. Comme notre image est en un seul bloc, nous devons +mettre ces paramètres à `0`. Enfin, les membres `imageSubResource`, `imageOffset` et `imageExtent` indiquent les parties +de l'image qui receveront les données. + +Les copies buffer vers image sont envoyées à la queue avec la fonction `vkCmdCopyBufferToImage`. + +```c++ +vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion +); +``` + +Le quatrième paramètre indique l'organisation de l'image au moment de la copie. Normalement l'image doit être dans +l'organisation optimale pour la réception de données. Nous avons paramétré la copie pour qu'un seul command buffer +soit à l'origine de la copie successive de tous les pixels. Nous aurions aussi pu créer un tableau de +`VkBufferImageCopy` pour que le command buffer soit à l'origine de plusieurs copies simultanées. + +## Préparer la texture d'image + +Nous avons maintenant tous les outils nécessaires pour compléter la mise en place de la texture d'image. Nous pouvons +retourner à la fonction `createTextureImage`. La dernière chose que nous y avions fait consistait à créer l'image +texture. Notre prochaine étape est donc d'y placer les pixels en les copiant depuis le buffer intermédiaire. Il y a deux +étapes pour cela : + +* Transitionner l'organisation de l'image vers `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` +* Exécuter le buffer de copie + +C'est simple à réaliser avec les fonctions que nous venons de créer : + +```c++ +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); +copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +``` + +Nous avons créé l'image avec une organisation `VK_LAYOUT_UNDEFINED`, car le contenu initial ne nous intéresse pas. + +Pour ensuite pouvoir échantillonner la texture depuis le fragment shader nous devons réaliser une dernière transition, +qui la préparera à être accédée depuis un shader : + +```c++ +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); +``` + +## Derniers champs de la barrière de transition + +Si vous lanciez le programme vous verrez que les validation layers vous indiquent que les champs d'accès et d'étapes +shader sont invalides. C'est normal, nous ne les avons pas remplis. + +Nous sommes pour le moment interessés par deux transitions : + +* Non défini → cible d'un transfert : écritures par transfert qui n'ont pas besoin d'être synchronisées +* Cible d'un transfert → lecture par un shader : la lecture par le shader doit attendre la fin du transfert + +Ces règles sont indiquées en utilisant les valeurs suivantes pour l'accès et les étapes shader : + +```c++ +VkPipelineStageFlags sourceStage; +VkPipelineStageFlags destinationStage; + +if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; +} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; +} else { + throw std::invalid_argument("transition d'orgisation non supportée!"); +} + +vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier +); +``` + +Comme vous avez pu le voir dans le tableau mentionné plus haut, l'écriture dans l'image doit se réaliser à l'étape +pipeline de transfert. Mais cette opération d'écriture ne dépend d'aucune autre opération. Nous pouvons donc fournir +une condition d'accès nulle et `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` comme opération pré-barrière. Cette valeur correspond +au début de la pipeline, mais ne représente pas vraiment une étape. Elle désigne plutôt le moment où la pipeline se +prépare, et donc sert communément aux transferts. Voyez +[la documentation](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#VkPipelineStageFlagBits) +pour de plus amples informations sur les pseudo-étapes. + +L'image sera écrite puis lue dans la même passe, c'est pourquoi nous devons indiquer que le fragment shader aura accès à +la mémoire de l'image. + +Quand nous aurons besoin de plus de transitions, nous compléterons la fonction de transition pour qu'elle les prenne en +compte. L'application devrait maintenant tourner sans problème, bien qu'il n'y aie aucune différence visible. + +Un point intéressant est que l'émission du command buffer génère implicitement une synchronisation de type +`VK_ACCESS_HOST_WRITE_BIT`. Comme la fonction `transitionImageLayout` exécute un command buffer ne comprenant qu'une +seule commande, il est possbile d'utiliser cette synchronisation. Cela signifie que vous pourriez alors mettre +`srcAccessMask` à `0` dans le cas d'une transition vers `VK_ACCESS_HOST_WRITE_BIT`. C'est à vous de voir si vous +voulez être explicites à ce sujet. Personnellement je n'aime pas du tout faire dépendre mon application sur des +opérations cachées, que je trouve dangereusement proche d'OpenGL. + +Autre chose intéressante à savoir, il existe une organisation qui supporte toutes les opérations. Elle s'appelle +`VK_IMAGE_LAYOUT_GENERAL`. Le problème est qu'elle est évidemment moins optimisée. Elle est cependant utile dans +certains cas, comme quand une image doit être utilisée comme cible et comme source, ou pour pouvoir lire l'image juste +après qu'elle aie quittée l'organisation préinitialisée. + +Enfin, il important de noter que les fonctions que nous avons mises en place exécutent les commandes de manière +synchronisées et attendent que la queue soit en pause. Pour de véritables applications il est bien sûr recommandé de +combiner toutes ces opérations dans un seul command buffer pour qu'elles soient exécutées de manière asynchrones. Les +commandes de transitions et de copie pourraient grandement bénéficier d'une telle pratique. Essayez par exemple de créer +une fonction `setupCommandBuffer`, puis d'enregistrer les commandes nécessaires depuis les fonctions actuelles. +Appelez ensuite une autre fonction nommée par exemple `flushSetupCommands` qui exécutera le command buffer. Avant +d'implémenter ceci attendez que nous ayons fait fonctionner l'échantillonage. + +## Nettoyage + +Complétez la fonction `createImageTexture` en libérant le buffer intermédiaire et en libérant la mémoire : + +```c++ + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); +} +``` + +L'image texture est utilisée jusqu'à la fin du programme, nous devons donc la libérer dans `cleanup` : + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + ... +} +``` + +L'image contient maintenant la texture, mais nous n'avons toujours pas mis en place de quoi y accéder depuis la +pipeline. Nous y travaillerons dans le prochain chapitre. + +[C++ code](/code/23_texture_image.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git a/fr/06_Texture_mapping/01_Vue_sur_image_et_sampler.md b/fr/06_Texture_mapping/01_Vue_sur_image_et_sampler.md new file mode 100644 index 00000000..cd67767b --- /dev/null +++ b/fr/06_Texture_mapping/01_Vue_sur_image_et_sampler.md @@ -0,0 +1,328 @@ +Dans ce chapitre nous allons créer deux nouvelles ressources dont nous aurons besoin pour pouvoir échantillonner une +image depuis la pipeline graphique. Nous avons déjà vu la première en travaillant avec la swap chain, mais la seconde +est nouvelle, et est liée à la manière dont le shader accédera aux texels de l'image. + +## Vue sur une image texture + +Nous avons vu précédemment que les images ne peuvent être accédées qu'à travers une vue. Nous aurons donc besoin de +créer une vue sur notre nouvelle image texture. + +Ajoutez un membre donnée pour stocker la référence à la vue de type `VkImageView`. Ajoutez ensuite la fonction +`createTextureImageView` qui créera cette vue. + +```c++ +VkImageView textureImageView; + +... + +void initVulkan() { + ... + createTextureImage(); + createTextureImageView(); + createVertexBuffer(); + ... +} + +... + +void createTextureImageView() { + +} +``` + +Le code de cette fonction peut être basé sur `createImageViews`. Les deux seuls changements sont dans `format` et +`image` : + +```c++ +VkImageViewCreateInfo viewInfo{}; +viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; +viewInfo.image = textureImage; +viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; +viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB; +viewInfo.components = VK_COMPONENT_SWIZZLE_IDENTITY; +viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +viewInfo.subresourceRange.baseMipLevel = 0; +viewInfo.subresourceRange.levelCount = 1; +viewInfo.subresourceRange.baseArrayLayer = 0; +viewInfo.subresourceRange.layerCount = 1; +``` + +Appellons `vkCreateImageView` pour finaliser la création de la vue : + +```c++ +if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) { + throw std::runtime_error("échec de la création d'une vue sur l'image texture!"); +} +``` + +Comme la logique est similaire à celle de `createImageViews`, nous ferions bien de la déplacer dans une fonction. Créez +donc `createImageView` : + +```c++ +VkImageView createImageView(VkImage image, VkFormat format) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("échec de la creation de la vue sur une image!"); + } + + return imageView; +} +``` + +Et ainsi `createTextureImageView` peut être réduite à : + +```c++ +void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB); +} +``` + +Et de même `createImageView` se résume à : + +```c++ +void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); + } +} +``` + +Préparons dès maintenant la libération de la vue sur l'image à la fin du programme, juste avant la destruction de +l'image elle-même. + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); +``` + +## Samplers + +Il est possible pour les shaders de directement lire les texels de l'image. Ce n'est cependant pas la technique +communément utilisée. Les textures sont généralement accédées à travers un sampler (ou échantillonneur) qui filtrera +et/ou transformera les données afin de calculer la couleur la plus désirable pour le pixel. + +Ces filtres sont utiles pour résoudre des problèmes tels que l'oversampling. Imaginez une texture que l'on veut mettre +sur de la géométrie possédant plus de fragments que la texture n'a de texels. Si le sampler se contentait de prendre +le pixel le plus proche, une pixellisation apparaît : + +![](/images/texture_filtering.png) + +En combinant les 4 texels les plus proches il est possible d'obtenir un rendu lisse comme présenté sur l'image de +droite. Bien sûr il est possible que votre application cherche plutôt à obtenir le premier résultat (Minecraft), mais +la seconde option est en général préférée. Un sampler applique alors automatiquement ce type d'opérations. + +L'undersampling est le problème inverse. Cela crée des artefacts particulièrement visibles dans le cas de textures +répétées vues à un angle aigu : + +![](/images/anisotropic_filtering.png) + +Comme vous pouvez le voir sur l'image de droite, la texture devient d'autant plus floue que l'angle de vision se réduit. +La solution à ce problème peut aussi être réalisée par le sampler et s'appelle +[anisotropic filtering](https://en.wikipedia.org/wiki/Anisotropic_filtering). Elle est par contre plus gourmande en +ressources. + +Au delà de ces filtres le sampler peut aussi s'occuper de transformations. Il évalue ce qui doit se passer quand le +fragment shader essaie d'accéder à une partie de l'image qui dépasse sa propre taille. Il se base sur le *addressing +mode* fourni lors de sa configuration. L'image suivante présente les différentes possiblités : + +![](/images/texture_addressing.png) + +Nous allons maintenant créer la fonction `createTextureSampler` pour mettre en place un sampler simple. Nous +l'utiliserons pour lire les couleurs de la texture. + +```c++ +void initVulkan() { + ... + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + ... +} + +... + +void createTextureSampler() { + +} +``` + +Les samplers se configurent avec une structure de type `VkSamplerCreateInfo`. Elle permet d'indiquer les filtres et les +transformations à appliquer. + +```c++ +VkSamplerCreateInfo samplerInfo{}; +samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; +samplerInfo.magFilter = VK_FILTER_LINEAR; +samplerInfo.minFilter = VK_FILTER_LINEAR; +``` + +Les membres `magFilter` et `minFilter` indiquent comment interpoler les texels respectivement magnifiés et minifiés, ce +qui correspond respectivement aux problèmes évoqués plus haut. Nous avons choisi `VK_FILTER_LINEAR`, qui indiquent +l'utilisation des méthodes pour régler les problèmes vus plus haut. + +```c++ +samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; +samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; +samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; +``` + +Le addressing mode peut être configuré pour chaque axe. Les axes disponibles sont indiqués ci-dessus ; notez +l'utilisation de U, V et W au lieu de X, Y et Z. C'est une convention dans le contexte des textures. Voilà les +différents modes possibles : + +* `VK_SAMPLER_ADDRESS_MODE_REPEAT` : répète le texture +* `VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT` : répète en inversant les coordonnées pour réaliser un effet miroir +* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE` : prend la couleur du pixel de bordure le plus proche +* `VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE` : prend la couleur de l'opposé du plus proche côté de l'image +* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER` : utilise une couleur fixée + +Le mode que nous utilisons n'est pas très important car nous ne dépasserons pas les coordonnées dans ce tutoriel. +Cependant le mode de répétition est le plus commun car il est infiniment plus efficace que d'envoyer plusieurs fois le +même carré à la pipeline, pour dessiner un pavage au sol par exemple. + +```c++ +samplerInfo.anisotropyEnable = VK_TRUE; +samplerInfo.maxAnisotropy = 16.0f; +``` + +Ces deux membres paramètrent l'utilisation de l'anistropic filtering. Il n'y a pas vraiment de raison de ne pas +l'utiliser, sauf si vous manquez de performances. Le champ `maxAnistropy` est le nombre maximal de texels utilisés pour +calculer la couleur finale. Une plus petite valeur permet d'augmenter les performances, mais résulte évidemment en une +qualité réduite. Il n'existe à ce jour aucune carte graphique pouvant utiliser plus de 16 texels car la qualité ne +change quasiment plus. + +```c++ +samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; +``` + +Le paramètre `borderColor` indique la couleur utilisée pour le sampling qui dépasse les coordonnées, si tel est le mode +choisi. Il est possible d'indiquer du noir, du blanc ou du transparent, mais vous ne pouvez pas indiquer une couleur +quelconque. + +```c++ +samplerInfo.unnormalizedCoordinates = VK_FALSE; +``` + +Le champ `unnomalizedCoordinates` indique le système de coordonnées que vous voulez utiliser pour accéder aux texels de +l'image. Avec `VK_TRUE`, vous pouvez utiliser des coordonnées dans `[0, texWidth)` et `[0, texHeight)`. Sinon, les +valeurs sont accédées avec des coordonnées dans `[0, 1)`. Dans la plupart des cas les coordonnées sont utilisées +normalisées car cela permet d'utiliser un même shader pour des textures de résolution différentes. + +```c++ +samplerInfo.compareEnable = VK_FALSE; +samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; +``` + +Si une fonction de comparaison est activée, les texels seront comparés à une valeur. Le résultat de la comparaison est +ensuite utilisé pour une opération de filtrage. Cette fonctionnalité est principalement utilisée pour réaliser +[un percentage-closer filtering](https://developer.nvidia.com/gpugems/GPUGems/gpugems_chll.html) sur les shadow maps. +Nous verrons cela dans un futur chapitre. + +```c++ +samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; +samplerInfo.mipLodBias = 0.0f; +samplerInfo.minLod = 0.0f; +samplerInfo.maxLod = 0.0f; +``` + +Tous ces champs sont liés au mipmapping. Nous y reviendrons dans un [prochain chapitre](/Generating_Mipmaps), mais pour +faire simple, c'est encore un autre type de filtre. + +Nous avons maintenant paramétré toutes les fonctionnalités du sampler. Ajoutez un membre donnée pour stocker la +référence à ce sampler, puis créez-le avec `vkCreateSampler` : + +```c++ +VkImageView textureImageView; +VkSampler textureSampler; + +... + +void createTextureSampler() { + ... + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("échec de la creation d'un sampler!"); + } +} +``` + +Remarquez que le sampler n'est pas lié à une quelconque `VkImage`. Il ne constitue qu'un objet distinct qui représente +une interface avec les images. Il peut être appliqué à n'importe quelle image 1D, 2D ou 3D. Cela diffère d'anciens APIs, +qui combinaient la texture et son filtrage. + +Préparons la destruction du sampler à la fin du programme : + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + ... +} +``` + +## Capacité du device à supporter l'anistropie + +Si vous lancez le programme, vous verrez que les validation layers vous envoient un message comme celui-ci : + +![](/images/validation_layer_anisotropy.png) + +En effet, l'anistropic filtering est une fonctionnalité du device qui doit être activée. Nous devons donc mettre à jour +la fonction `createLogicalDevice` : + +```c++ +VkPhysicalDeviceFeatures deviceFeatures{}; +deviceFeatures.samplerAnisotropy = VK_TRUE; +``` + +Et bien qu'il soit très peu probable qu'une carte graphique moderne ne supporte pas cette fonctionnalité, nous devrions +aussi adapter `isDeviceSuitable` pour en être sûr. + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + ... + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; +} +``` + +La structure `VkPhysicalDeviceFeatures` permet d'indiquer les capacités supportées quand elle est utilisée avec la +fonction `VkPhysicalDeviceFeatures`, plutôt que de fournir ce dont nous avons besoin. + +Au lieu de simplement obliger le client à posséder une carte graphique supportant l'anistropic filtering, nous pourrions +conditionnellement activer ou pas l'anistropic filtering : + +```c++ +samplerInfo.anisotropyEnable = VK_FALSE; +samplerInfo.maxAnisotropy = 1.0f; +``` + +Dans le prochain chapitre nous exposerons l'image et le sampler au fragment shader pour qu'il puisse utiliser la +texture sur le carré. + +[C++ code](/code/24_sampler.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git "a/fr/06_Texture_mapping/02_Sampler_d'image_combin\303\251.md" "b/fr/06_Texture_mapping/02_Sampler_d'image_combin\303\251.md" new file mode 100644 index 00000000..3f7043d7 --- /dev/null +++ "b/fr/06_Texture_mapping/02_Sampler_d'image_combin\303\251.md" @@ -0,0 +1,260 @@ +## Introduction + +Nous avons déjà évoqué les descripteurs dans la partie sur les buffers d'uniformes. Dans ce chapitre nous en verrons un +nouveau type : les *samplers d'image combinés* (*combined image sampler*). Ceux-ci permettent aux shaders d'accéder au +contenu d'images, à travers un sampler. + +Nous allons d'abord modifier l'organisation des descripteurs, la pool de descripteurs et le set de descripteurs pour +qu'ils incluent le sampler d'image combiné. Ensuite nous ajouterons des coordonnées de texture à la structure +`Vertex` et modifierons le vertex shader et le fragment shader pour qu'il utilisent les couleurs de la texture. + +## Modifier les descripteurs + +Trouvez la fonction `createDescriptorSetLayout` et créez une instance de `VkDescriptorSetLayoutBinding`. Cette +structure correspond aux descripteurs d'image combinés. Nous n'avons quasiment que l'indice du binding à y mettre : + +```c++ +VkDescriptorSetLayoutBinding samplerLayoutBinding{}; +samplerLayoutBinding.binding = 1; +samplerLayoutBinding.descriptorCount = 1; +samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; +samplerLayoutBinding.pImmutableSamplers = nullptr; +samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + +std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; +VkDescriptorSetLayoutCreateInfo layoutInfo{}; +layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; +layoutInfo.bindingCount = static_cast(bindings.size()); +layoutInfo.pBindings = bindings.data(); +``` + +Assurez-vous également de bien indiquer le fragment shader dans le champ `stageFlags`. Ce sera à cette étape que la +couleur sera extraite de la texture. Il est également possible d'utiliser le sampler pour échantilloner une texture dans +le vertex shader. Cela permet par exemple de déformer dynamiquement une grille de vertices pour réaliser une +[heightmap](https://en.wikipedia.org/wiki/Heightmap) à partir d'une texture de vecteurs. + +Si vous lancez l'application, vous verrez que la pool de descripteurs ne peut pas allouer de set avec l'organisation que +nous avons préparée, car elle ne comprend aucun descripteur de sampler d'image combiné. Il nous faut donc modifier la +fonction `createDescriptorPool` pour qu'elle inclue une structure `VkDesciptorPoolSize` qui corresponde à ce type de +descripteur : + +```c++ +std::array poolSizes{}; +poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +poolSizes[0].descriptorCount = static_cast(swapChainImages.size()); +poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; +poolSizes[1].descriptorCount = static_cast(swapChainImages.size()); + +VkDescriptorPoolCreateInfo poolInfo{}; +poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; +poolInfo.poolSizeCount = static_cast(poolSizes.size()); +poolInfo.pPoolSizes = poolSizes.data(); +poolInfo.maxSets = static_cast(swapChainImages.size()); +``` + +La dernière étape consiste à lier l'image et le sampler aux descripteurs du set de descripteurs. Allez à la fonction +`createDescriptorSets`. + +```c++ +for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo{}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + ... +} +``` + +Les ressources nécessaires à la structure paramétrant un descripteur d'image combiné doivent être fournies dans +une structure de type `VkDescriptorImageInfo`. Cela est similaire à la création d'un descripteur pour buffer. Les objets +que nous avons créés dans les chapitres précédents s'assemblent enfin! + +```c++ +std::array descriptorWrites{}; + +descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +descriptorWrites[0].dstSet = descriptorSets[i]; +descriptorWrites[0].dstBinding = 0; +descriptorWrites[0].dstArrayElement = 0; +descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +descriptorWrites[0].descriptorCount = 1; +descriptorWrites[0].pBufferInfo = &bufferInfo; + +descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +descriptorWrites[1].dstSet = descriptorSets[i]; +descriptorWrites[1].dstBinding = 1; +descriptorWrites[1].dstArrayElement = 0; +descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; +descriptorWrites[1].descriptorCount = 1; +descriptorWrites[1].pImageInfo = &imageInfo; + +vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); +``` + +Les descripteurs doivent être mis à jour avec des informations sur l'image, comme pour les buffers. Cette fois nous +allons utiliser le tableau `pImageInfo` plutôt que `pBufferInfo`. Les descripteurs sont maintenant prêts à l'emploi. + +## Coordonnées de texture + +Il manque encore un élément au mapping de textures. Ce sont les coordonnées spécifiques aux sommets. Ce sont elles qui +déterminent les coordonnées de la texture à lier à la géométrie. + +```c++ +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } +}; +``` + +Modifiez la structure `Vertex` pour qu'elle comprenne un `vec2`, qui servira à contenir les coordonnées de texture. +Ajoutez également un `VkVertexInputAttributeDescription` afin que ces coordonnées puissent être accédées en entrée du +vertex shader. Il est nécessaire de les passer du vertex shader vers le fragment shader afin que l'interpolation les +transforment en un gradient. + +```c++ +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}} +}; +``` + +Dans ce tutoriel nous nous contenterons de mettre une texture sur le carré en utilisant des coordonnées normalisées. +Nous mettrons le `0, 0` en haut à gauche et le `1, 1` en bas à droite. Essayez de mettre des valeurs sous `0` ou au-delà +de `1` pour voir l'addressing mode en action. Vous pourrez également changer le mode dans la création du sampler pour +voir comment ils se comportent. + +## Shaders + +La dernière étape consiste à modifier les shaders pour qu'ils utilisent la texture et non les couleurs. Commençons par +le vertex shader : + +```glsl +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} +``` + +Comme pour les couleurs spécifiques aux vertices, les valeurs `fragTexCoord` seront interpolées dans le carré par +le rasterizer pour créer un gradient lisse. Le résultat de l'interpolation peut être visualisé en utilisant les +coordonnées comme couleurs : + +```glsl +#version 450 + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragTexCoord, 0.0, 1.0); +} +``` + +Vous devriez avoir un résultat similaire à l'image suivante. N'oubliez pas de recompiler les shader! + +![](/images/texcoord_visualization.png) + +Le vert représente l'horizontale et le rouge la verticale. Les coins noirs et jaunes confirment la normalisation des +valeurs de `0, 0` à `1, 1`. Utiliser les couleurs pour visualiser les valeurs et déboguer est similaire à utiliser +`printf`. C'est peu pratique mais il n'y a pas vraiment d'autre option. + +Un descripteur de sampler d'image combiné est représenté dans les shaders par un objet de type `sampler` placé dans +une variable uniforme. Créez donc une variable `texSampler` : + +```glsl +layout(binding = 1) uniform sampler2D texSampler; +``` + +Il existe des équivalents 1D et 3D pour de telles textures. + +```glsl +void main() { + outColor = texture(texSampler, fragTexCoord); +} +``` + +Les textures sont échantillonées à l'aide de la fonction `texture`. Elle prend en argument un objet `sampler` et des +coordonnées. Le sampler exécute les transformations et le filtrage en arrière-plan. Vous devriez voir la texture sur le +carré maintenant! + +![](/images/texture_on_square.png) + +Expérimentez avec l'addressing mode en fournissant des valeurs dépassant `1`, et vous verrez la répétition de texture à +l'oeuvre : + +```glsl +void main() { + outColor = texture(texSampler, fragTexCoord * 2.0); +} +``` + +![](/images/texture_on_square_repeated.png) + +Vous pouvez aussi combiner les couleurs avec celles écrites à la main : + +```glsl +void main() { + outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0); +} +``` + +J'ai séparé l'alpha du reste pour ne pas altérer la transparence. + +![](/images/texture_on_square_colorized.png) + +Nous pouvons désormais utiliser des textures dans notre programme! Cette technique est extrêmement puissante et permet +beaucoup plus que juste afficher des couleurs. Vous pouvez même utiliser les images de la swap chain comme textures et y +appliquer des effets post-processing. + +[Code C++](/code/25_texture_mapping.cpp) / +[Vertex shader](/code/25_shader_textures.vert) / +[Fragment shader](/code/25_shader_textures.frag) diff --git a/fr/07_Buffer_de_profondeur.md b/fr/07_Buffer_de_profondeur.md new file mode 100644 index 00000000..38923608 --- /dev/null +++ b/fr/07_Buffer_de_profondeur.md @@ -0,0 +1,550 @@ +## Introduction + +Jusqu'à présent nous avons projeté notre géométrie en 3D, mais elle n'est toujours définie qu'en 2D. Nous allons ajouter +l'axe Z dans ce chapitre pour permettre l'utilisation de modèles 3D. Nous placerons un carré au-dessus ce celui que nous +avons déjà, et nous verrons ce qui se passe si la géométrie n'est pas organisée par profondeur. + +## Géométrie en 3D + +Mettez à jour la structure `Vertex` pour que les coordonnées soient des vecteurs à 3 dimensions. Il faut également +changer le champ `format` dans la structure `VkVertexInputAttributeDescription` correspondant aux coordonnées : + +```c++ +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + ... + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + ... + } +}; +``` + +Mettez également à jour l'entrée du vertex shader qui correspond aux coordonnées. Recompilez le shader. + +```glsl +layout(location = 0) in vec3 inPosition; + +... + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} +``` + +Enfin, il nous faut ajouter la profondeur là où nous créons les instances de `Vertex`. + +```c++ +const std::vector vertices = { + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} +}; +``` + +Si vous lancez l'application vous verrez exactement le même résultat. Il est maintenant temps d'ajouter de la géométrie +pour rendre la scène plus intéressante, et pour montrer le problème évoqué plus haut. Dupliquez les vertices afin qu'un +second carré soit rendu au-dessus de celui que nous avons maintenant : + +![](/images/extra_square.svg) + +Nous allons utiliser `-0.5f` comme coordonnée Z. + +```c++ +const std::vector vertices = { + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + + {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4 +}; +``` + +Si vous lancez le programme maintenant vous verrez que le carré d'en-dessous est rendu au-dessus de l'autre : + +![](/images/depth_issues.png) + +Ce problème est simplement dû au fait que le carré d'en-dessous est placé après dans le tableau des vertices. Il y a +deux manières de régler ce problème : + +* Trier tous les appels en fonction de la profondeur +* Utiliser un buffer de profondeur + +La première approche est communément utilisée pour l'affichage d'objets transparents, car la transparence non ordonnée +est un problème difficile à résoudre. Cependant, pour la géométrie sans transparence, le buffer de profondeur est un +très bonne solution. Il consiste en un attachement supplémentaire au framebuffer, qui stocke les profondeurs. La +profondeur de chaque fragment produit par le rasterizer est comparée à la valeur déjà présente dans le buffer. Si le +fragment est plus distant que celui déjà traité, il est simplement éliminé. Il est possible de manipuler cette valeur de +la même manière que la couleur. + +```c++ +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +``` + +La matrice de perspective générée par GLM utilise par défaut la profondeur OpenGL comprise en -1 et 1. Nous pouvons +configurer GLM avec `GLM_FORCE_DEPTH_ZERO_TO_ONE` pour qu'elle utilise des valeurs correspondant à Vulkan. + +## Image de pronfondeur et views sur cette image + +L'attachement de profondeur est une image. La différence est que celle-ci n'est pas créée par la swap chain. Nous +n'avons besoin que d'un seul attachement de profondeur, car les opérations sont séquentielles. L'attachement aura +encore besoin des trois mêmes ressources : une image, de la mémoire et une image view. + +```c++ +VkImage depthImage; +VkDeviceMemory depthImageMemory; +VkImageView depthImageView; +``` + +Créez une nouvelle fonction `createDepthResources` pour mettre en place ces ressources : + +```c++ +void initVulkan() { + ... + createCommandPool(); + createDepthResources(); + createTextureImage(); + ... +} + +... + +void createDepthResources() { + +} +``` + +La création d'une image de profondeur est assez simple. Elle doit avoir la même résolution que l'attachement de couleur, +définie par l'étendue de la swap chain. Elle doit aussi être configurée comme image de profondeur, avoir un tiling +optimal et une mémoire placée sur la carte graphique. Une question persiste : quelle est l'organisation optimale pour +une image de profondeur? Le format contient un composant de profondeur, indiqué par `_Dxx_` dans les valeurs de type +`VK_FORMAT`. + +Au contraire de l'image de texture, nous n'avons pas besoin de déterminer le format requis car nous n'accéderons pas à +cette texture nous-mêmes. Nous n'avons besoin que d'une précision suffisante, en général un minimum de 24 bits. Il y a +plusieurs formats qui satisfont cette nécéssité : + +* `VK_FORMAT_D32_SFLOAT` : float signé de 32 bits pour la profondeur +* `VK_FORMAT_D32_SFLOAT_S8_UINT` : float signé de 32 bits pour la profondeur et int non signé de 8 bits pour le stencil +* `VK_FORMAT_D24_UNORM_S8_UINT` : float signé de 24 bits pour la profondeur et int non signé de 8 bits pour le stencil + +Le composant de stencil est utilisé pour le [test de stencil](https://en.wikipedia.org/wiki/Stencil_buffer). C'est un +test additionnel qui peut être combiné avec le test de profondeur. Nous y reviendrons dans un futur chapitre. + +Nous pourrions nous contenter d'utiliser `VK_FORMAT_D32_SFLOAT` car son support est pratiquement assuré, mais il est +préférable d'utiliser une fonction pour déterminer le meilleur format localement supporté. Créez pour cela la fonction +`findSupportedFormat`. Elle vérifiera que les formats en argument sont supportés et choisira le meilleur en se basant +sur leur ordre dans le vecteurs des formats acceptables fourni en argument : + +```c++ +VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + +} +``` + +Leur support dépend du mode de tiling et de l'usage, nous devons donc les transmettre en argument. Le support des +formats peut ensuite être demandé à l'aide de la fonction `vkGetPhysicalDeviceFormatProperties` : + +```c++ +for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); +} +``` + +La structure `VkFormatProperties` contient trois champs : + +* `linearTilingFeatures` : utilisations supportées avec le tiling linéaire +* `optimalTilingFeatures` : utilisations supportées avec le tiling optimal +* `bufferFeatures` : utilisations supportées avec les buffers + +Seuls les deux premiers cas nous intéressent ici, et celui que nous vérifierons dépendra du mode de tiling fourni en +paramètre. + +```c++ +if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; +} else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; +} +``` + +Si aucun des candidats ne supporte l'utilisation désirée, nous pouvons lever une exception. + +```c++ +VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("aucun des formats demandés n'est supporté!"); +} +``` + +Nous allons utiliser cette fonction depuis une autre fonction `findDepthFormat`. Elle sélectionnera un format +avec un composant de profondeur qui supporte d'être un attachement de profondeur : + +```c++ +VkFormat findDepthFormat() { + return findSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); +} +``` + +Utilisez bien `VK_FORMAT_FEATURE_` au lieu de `VK_IMAGE_USAGE_`. Tous les candidats contiennent la profondeur, mais +certains ont le stencil en plus. Ainsi il est important de voir que dans ce cas, la profondeur n'est qu'une *capacité* +et non un *usage* exclusif. Autre point, nous devons prendre cela en compte pour les transitions d'organisation. Ajoutez +une fonction pour determiner si le format contient un composant de stencil ou non : + +```c++ +bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; +} +``` + +Appelez cette fonction depuis `createDepthResources` pour déterminer le format de profondeur : + +```c++ +VkFormat depthFormat = findDepthFormat(); +``` + +Nous avons maintenant toutes les informations nécessaires pour invoquer `createImage` et `createImageView`. + +```c++ +createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); +depthImageView = createImageView(depthImage, depthFormat); +``` + +Cependant cette fonction part du principe que la `subresource` est toujours `VK_IMAGE_ASPECT_COLOR_BIT`, il nous faut +donc en faire un paramètre. + +```c++ +VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { + ... + viewInfo.subresourceRange.aspectMask = aspectFlags; + ... +} +``` + +Changez également les appels à cette fonction pour prendre en compte ce changement : + +```c++ +swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); +... +depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); +... +textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); +``` + +Voilà tout pour la création de l'image de profondeur. Nous n'avons pas besoin d'y envoyer de données ou quoi que ce soit +de ce genre, car nous allons l'initialiser au début de la render pass tout comme l'attachement de couleur. + +### Explicitement transitionner l'image de profondeur + +Nous n'avons pas besoin de faire explicitement la transition du layout de l'image vers un attachement de profondeur parce +qu'on s'en occupe directement dans la render pass. En revanche, pour l'exhaustivité je vais quand même vous décrire le processus +dans cette section. Vous pouvez sauter cette étape si vous le souhaitez. + +Faites un appel à `transitionImageLayout` à la fin de `createDepthResources` comme ceci: + +```c++ +transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); +``` + +L'organisation indéfinie peut être utilisée comme organisation intiale, dans la mesure où aucun contenu d'origine n'a +d'importance. Nous devons faire évaluer la logique de `transitionImageLayout` pour qu'elle puisse utiliser la +bonne subresource. + +```c++ +if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + + if (hasStencilComponent(format)) { + barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } +} else { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +} +``` + +Même si nous n'utilisons pas le composant de stencil, nous devons nous en occuper dans les transitions de l'image de +profondeur. + +Ajoutez enfin le bon accès et les bonnes étapes pipeline : + +```c++ +if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; +} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; +} else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; +} else { + throw std::invalid_argument("transition d'organisation non supportée!"); +} +``` + +Le buffer de profondeur sera lu avant d'écrire un fragment, et écrit après qu'un fragment valide soit traité. La lecture +se passe en `VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT` et l'écriture en `VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT`. +Vous devriez choisir la première des étapes correspondant à l'opération correspondante, afin que tout soit prêt pour +l'utilisation de l'attachement de profondeur. + +## Render pass + +Nous allons modifier `createRenderPass` pour inclure l'attachement de profondeur. Spécifiez d'abord un +`VkAttachementDescription` : + +```c++ +VkAttachmentDescription depthAttachment{}; +depthAttachment.format = findDepthFormat(); +depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; +depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; +depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; +depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; +depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; +depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; +``` + +Le `format` doit être celui de l'image de profondeur. Pour cette fois nous ne garderons pas les données de profondeur, +car nous n'en avons plus besoin après le rendu. Encore une fois le hardware pourra réaliser des optimisations. Et +de même nous n'avons pas besoin des valeurs du rendu précédent pour le début du rendu de la frame, nous pouvons donc +mettre `VK_IMAGE_LAYOUT_UNDEFINED` comme valeur pour `initialLayout`. + +```c++ +VkAttachmentReference depthAttachmentRef{}; +depthAttachmentRef.attachment = 1; +depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; +``` + +Ajoutez une référence à l'attachement dans notre seule et unique subpasse : + +```c++ +VkSubpassDescription subpass{}; +subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; +subpass.colorAttachmentCount = 1; +subpass.pColorAttachments = &colorAttachmentRef; +subpass.pDepthStencilAttachment = &depthAttachmentRef; +``` + +Les subpasses ne peuvent utiliser qu'un seul attachement de profondeur (et de stencil). Réaliser le test de profondeur +sur plusieurs buffers n'a de toute façon pas beaucoup de sens. + +```c++ +std::array attachments = {colorAttachment, depthAttachment}; +VkRenderPassCreateInfo renderPassInfo{}; +renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; +renderPassInfo.attachmentCount = static_cast(attachments.size()); +renderPassInfo.pAttachments = attachments.data(); +renderPassInfo.subpassCount = 1; +renderPassInfo.pSubpasses = &subpass; +renderPassInfo.dependencyCount = 1; +renderPassInfo.pDependencies = &dependency; +``` + +Changez enfin la structure `VkRenderPassCreateInfo` pour qu'elle se réfère aux deux attachements. + +## Framebuffer + +L'étape suivante va consister à modifier la création du framebuffer pour lier notre image de profondeur à l'attachement +de profondeur. Trouvez `createFramebuffers` et indiquez la view sur l'image de profondeur comme second attachement : + +```c++ +std::array attachments = { + swapChainImageViews[i], + depthImageView +}; + +VkFramebufferCreateInfo framebufferInfo{}; +framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; +framebufferInfo.renderPass = renderPass; +framebufferInfo.attachmentCount = static_cast(attachments.size()); +framebufferInfo.pAttachments = attachments.data(); +framebufferInfo.width = swapChainExtent.width; +framebufferInfo.height = swapChainExtent.height; +framebufferInfo.layers = 1; +``` + +L'attachement de couleur doit différer pour chaque image de la swap chain, mais l'attachement de profondeur peut être le +même pour toutes, car il n'est utilisé que par la subpasse, et la synchronisation que nous avons mise en place ne permet +pas l'exécution de plusieurs subpasses en même temps. + +Nous devons également déplacer l'appel à `createFramebuffers` pour que la fonction ne soit appelée qu'après la création +de l'image de profondeur : + +```c++ +void initVulkan() { + ... + createDepthResources(); + createFramebuffers(); + ... +} +``` + +## Supprimer les valeurs + +Comme nous avons plusieurs attachements avec `VK_ATTACHMENT_LOAD_OP_CLEAR`, nous devons spécifier plusieurs valeurs de +suppression. Allez à `createCommandBuffers` et créez un tableau de `VkClearValue` : + +```c++ +std::array clearValues{}; +clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; +clearValues[1].depthStencil = {1.0f, 0}; + +renderPassInfo.clearValueCount = static_cast(clearValues.size()); +renderPassInfo.pClearValues = clearValues.data(); +``` + +Avec Vulkan, `0.0` correspond au plan near et `1.0` au plan far. La valeur initiale doit donc être `1.0`, afin que tout +fragment puisse s'y afficher. Notez que l'ordre des `clearValues` correspond à l'ordre des attachements auquelles les +couleurs correspondent. + +## État de profondeur et de stencil + +L'attachement de profondeur est prêt à être utilisé, mais le test de profondeur n'a pas encore été activé. Il est +configuré à l'aide d'une structure de type `VkPipelineDepthStencilStateCreateInfo`. + +```c++ +VkPipelineDepthStencilStateCreateInfo depthStencil{}; +depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; +depthStencil.depthTestEnable = VK_TRUE; +depthStencil.depthWriteEnable = VK_TRUE; +``` + +Le champ `depthTestEnable` permet d'activer la comparaison de la profondeur des fragments. Le champ `depthWriteEnable` +indique si la nouvelle profondeur des fragments qui passent le test doivent être écrite dans le tampon de profondeur. + +```c++ +depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; +``` + +Le champ `depthCompareOp` permet de fournir le test de comparaison utilisé pour conserver ou éliminer les fragments. +Nous gardons le `<` car il correspond le mieux à la convention employée par Vulkan. + +```c++ +depthStencil.depthBoundsTestEnable = VK_FALSE; +depthStencil.minDepthBounds = 0.0f; // Optionnel +depthStencil.maxDepthBounds = 1.0f; // Optionnel +``` + +Les champs `depthBoundsTestEnable`, `minDepthBounds` et `maxDepthBounds` sont utilisés pour des tests optionnels +d'encadrement de profondeur. Ils permettent de ne garder que des fragments dont la profondeur est comprise entre deux +valeurs fournies ici. Nous n'utiliserons pas cette fonctionnalité. + +```c++ +depthStencil.stencilTestEnable = VK_FALSE; +depthStencil.front = {}; // Optionnel +depthStencil.back = {}; // Optionnel +``` + +Les trois derniers champs configurent les opérations du buffer de stencil, que nous n'utiliserons pas non plus dans ce +tutoriel. Si vous voulez l'utiliser, vous devrez vous assurer que le format sélectionné pour la profondeur contient +aussi un composant pour le stencil. + +```c++ +pipelineInfo.pDepthStencilState = &depthStencil; +``` + +Mettez à jour la création d'une instance de `VkGraphicsPipelineCreateInfo` pour référencer l'état de profondeur et de +stencil que nous venons de créer. Un tel état doit être spécifié si la passe contient au moins l'une de ces +fonctionnalités. + +Si vous lancez le programme, vous verrez que la géométrie est maintenant correctement rendue : + +![](/images/depth_correct.png) + +## Gestion des redimensionnements de la fenêtre + +La résolution du buffer de profondeur doit changer avec la fenêtre quand elle redimensionnée, pour pouvoir correspondre +à la taille de l'attachement. Étendez `recreateSwapChain` pour régénérer les ressources : + +```c++ +void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createDepthResources(); + createFramebuffers(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); +} +``` + +La libération des ressources doit avoir lieu dans la fonction de libération de la swap chain. + +```c++ +void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + ... +} +``` + +Votre application est maintenant capable de rendre correctement de la géométrie 3D! Nous allons utiliser cette +fonctionnalité pour afficher un modèle dans le prohain chapitre. + +[Code C++](/code/26_depth_buffering.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git "a/fr/08_Charger_des_mod\303\250les.md" "b/fr/08_Charger_des_mod\303\250les.md" new file mode 100644 index 00000000..04cdfda1 --- /dev/null +++ "b/fr/08_Charger_des_mod\303\250les.md" @@ -0,0 +1,286 @@ +## Introduction + +Votre programme peut maintenant réaliser des rendus 3D, mais la géométrie que nous utilisons n'est pas très +intéressante. Nous allons maintenant étendre notre programme pour charger les sommets depuis des fichiers. Votre carte +graphique aura enfin un peu de travail sérieux à faire. + +Beaucoup de tutoriels sur les APIs graphiques font implémenter par le lecteur un système pour charger les modèle OBJ. Le +problème est que ce type de fichier est limité. Nous *allons* charger des modèles en OBJ, mais nous nous concentrerons +plus sur l'intégration des sommets dans le programme, plutôt que sur les aspects spécifiques de ce format de fichier. + +## Une librairie + +Nous utiliserons la librairie [tinyobjloader](https://github.com/syoyo/tinyobjloader) pour charger les vertices et les +faces depuis un fichier OBJ. Elle est facile à utiliser et à intégrer, car elle est contenue dans un seul fichier. +Téléchargez-la depuis le lien GitHub, elle est contenue dans le fichier `tiny_obj_loader.h`. + +**Visual Studio** + +Ajoutez dans `Additional Include Directories` le dossier dans lequel est contenu `tiny_obj_loader.h`. + +![](/images/include_dirs_tinyobjloader.png) + +**Makefile** + +Ajoutez le dossier contenant `tiny_obj_loader.h` aux dossiers d'inclusions de GCC : + +```text +VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64 +STB_INCLUDE_PATH = /home/user/libraries/stb +TINYOBJ_INCLUDE_PATH = /home/user/libraries/tinyobjloader + +... + +CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) -I$(TINYOBJ_INCLUDE_PATH) +``` + +## Exemple de modèle + +Nous n'allons pas utiliser de lumières pour l'instant. Il est donc préférable de charger un modèle qui comprend les +ombres pour que nous ayons un rendu plus intéressant. Vous pouvez trouver de tels modèles sur +[Sketchfab](https://sketchfab.com/). + +Pour ce tutoriel j'ai choisi d'utiliser le [Viking room](https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38) créé par [nigelgoh](https://sketchfab.com/nigelgoh) ([CC BY 4.0](https://web.archive.org/web/20200428202538/https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38)). +J'en ai changé la taille et l'orientation pour l'utiliser comme remplacement de notre géométrie actuelle : + +* [viking_room.obj](/resources/viking_room.obj) +* [viking_room.png](/resources/viking_room.png) + +Il possède un demi-million de triangles, ce qui fera un bon test pour notre application. Vous pouvez utiliser un +autre modèle si vous le désirez, mais assurez-vous qu'il ne comprend qu'un seul matériau et que ses dimensions sont +d'approximativement 1.5 x 1.5 x 1.5. Si il est plus grand vous devrez changer la matrice view. Mettez le modèle dans un +dossier appelé `models`, et placez l'image dans le dossier `textures`. + +Ajoutez deux variables de configuration pour la localisation du modèle et de la texture : + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +``` + +Changez la fonction `createTextureImage` pour qu'elle utilise cette seconde constante pour charger la texture. + +```c++ +stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); +``` + +## Charger les vertices et les indices + +Nous allons maintenant charger les vertices et les indices depuis le fichier OBJ. Supprimez donc les tableaux +`vertices` et `indices`, et remplacez-les par des vecteurs dynamiques : + +```c++ +std::vector vertices; +std::vector indices; +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; +``` + +Il faut aussi que le type des indices soit maintenant un `uint32_t` car nous allons avoir plus que 65535 sommets. +Changez également le paramètre de type dans l'appel à `vkCmdBindIndexBuffer`. + +```c++ +vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); +``` + +La librairie que nous utilisons s'inclue de la même manière que les librairies STB. Il faut définir la macro +`TINYOBJLOADER_IMLEMENTATION` pour que le fichier comprenne les définitions des fonctions. + +```c++ +#define TINYOBJLOADER_IMPLEMENTATION +#include +``` + +Nous allons ensuite écrire la fonction `loadModel` pour remplir le tableau de vertices et d'indices depuis le fichier +OBJ. Nous devons l'appeler avant que les buffers de vertices et d'indices soient créés. + +```c++ +void initVulkan() { + ... + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + ... +} + +... + +void loadModel() { + +} +``` + +Un modèle se charge dans la librairie avec la fonction `tinyobj::LoadObj` : + +```c++ +void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(err); + } +} +``` + +Dans un fichier OBJ on trouve des positions, des normales, des coordonnées de textures et des faces. Ces dernières +sont une collection de vertices, avec chaque vertex lié à une position, une normale et/ou un coordonnée de texture à +l'aide d'un indice. Il est ainsi possible de réutiliser les attributs de manière indépendante. + +Le conteneur `attrib` contient les positions, les normales et les coordonnées de texture dans les vecteurs +`attrib.vertices`, `attrib.normals` et `attrib.texcoords`. Le conteneur `shapes` contient tous les objets et leurs +faces. Ces dernières se réfèrent donc aux données stockées dans `attrib`. Les modèles peuvent aussi définir un matériau +et une texture par face, mais nous ignorerons ces attributs pour le moment. + +La chaîne de caractères `err` contient les erreurs et les messages générés pendant le chargement du fichier. Le +chargement des fichiers ne rate réellement que quand `LoadObj` retourne `false`. Les faces peuvent être constitués d'un +nombre quelconque de vertices, alors que notre application ne peut dessiner que des triangles. Heureusement, la fonction +possède la capacité - activée par défaut - de triangulariser les faces. + +Nous allons combiner toutes les faces du fichier en un seul modèle. Commençons par itérer sur ces faces. + +```c++ +for (const auto& shape : shapes) { + +} +``` + +Grâce à la triangularisation nous sommes sûrs que les faces n'ont que trois vertices. Nous pouvons donc simplement les +copier vers le vecteur des vertices finales : + +```c++ +for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertices.push_back(vertex); + indices.push_back(indices.size()); + } +} +``` + +Pour faire simple nous allons partir du principe que les sommets sont uniques. La variable `index` est du type +`tinyobj::index_t`, et contient `vertex_index`, `normal_index` et `texcoord_index`. Nous devons traiter ces données +pour les relier aux données contenues dans les tableaux `attrib` : + +```c++ +vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] +}; + +vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + attrib.texcoords[2 * index.texcoord_index + 1] +}; + +vertex.color = {1.0f, 1.0f, 1.0f}; +``` + +Le tableau `attrib.vertices` est constitués de floats et non de vecteurs à trois composants comme `glm::vec3`. Il faut +donc multiplier les indices par 3. De même on trouve deux coordonnées de texture par entrée. Les décalages `0`, `1` et +`2` permettent ensuite d'accéder aux composant X, Y et Z, ou aux U et V dans le cas des textures. + +Lancez le programme avec les optimisation activées (`Release` avec Visual Studio ou avec l'argument `-03` pour GCC). +Vous pourriez le faire sans mais le chargement du modèle sera très long. Vous devriez voir ceci : + +![](/images/inverted_texture_coordinates.png) + +La géométrie est correcte! Par contre les textures sont quelque peu... étranges. En effet le format OBJ part d'en bas à +gauche pour les coordonnées de texture, alors que Vulkan part d'en haut à gauche. Il suffit de changer cela pendant le +chargement du modèle : + +```c++ +vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] +}; +``` + +Vous pouvez lancer à nouveau le programme. Le rendu devrait être correct : + +![](/images/drawing_model.png) + +## Déduplication des vertices + +Pour le moment nous n'utilisons pas l'index buffer, et le vecteur `vertices` contient beaucoup de vertices dupliquées. +Nous ne devrions les inclure qu'une seule fois dans ce conteneur et utiliser leurs indices pour s'y référer. Une +manière simple de procéder consiste à utiliser une `unoredered_map` pour suivre les vertices multiples et leurs indices. + +```c++ +#include + +... + +std::unordered_map uniqueVertices{}; + +for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + ... + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } +} +``` + +Chaque fois que l'on extrait un vertex du fichier, nous devons vérifier si nous avons déjà manipulé un vertex possédant +les mêmes attributs. Si il est nouveau, nous le stockerons dans `vertices` et placerons son indice dans +`uniqueVertices` et dans `indices`. Si nous avons déjà un tel vertex nous regarderons son indice depuis `uniqueVertices` +et copierons cette valeur dans `indices`. + +Pour l'instant le programme ne peut pas compiler, car nous devons implémenter une fonction de hachage et l'opérateur +d'égalité pour utiliser la structure `Vertex` comme clé dans une table de hachage. L'opérateur est simple à surcharger : + +```c++ +bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; +} +``` + +Nous devons définir une spécialisation du patron de classe `std::hash` pour la fonction de hachage. Le hachage est +un sujet compliqué, mais [cppreference.com recommande](http://en.cppreference.com/w/cpp/utility/hash) l'approche +suivante pour combiner correctement les champs d'une structure : + +```c++ +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ + (hash()(vertex.color) << 1)) >> 1) ^ + (hash()(vertex.texCoord) << 1); + } + }; +} +``` + +Ce code doit être placé hors de la définition de `Vertex`. Les fonctions de hashage des type GLM sont activés avec +la définition et l'inclusion suivantes : + +```c++ +#define GLM_ENABLE_EXPERIMENTAL +#include +``` + +Le dossier `glm/gtx/` contient les extensions expérimentales de GLM. L'API peut changer dans le futur, mais la +librairie a toujours été très stable. + +Vous devriez pouvoir compiler et lancer le programme maintenant. Si vous regardez la taille de `vertices` vous verrez +qu'elle est passée d'un million et demi vertices à seulement 265645! Les vertices sont utilisés pour six triangles en +moyenne, ce qui représente une optimisation conséquente. + +[Code C++](/code/27_model_loading.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git "a/fr/09_G\303\251n\303\251rer_des_mipmaps.md" "b/fr/09_G\303\251n\303\251rer_des_mipmaps.md" new file mode 100644 index 00000000..7d328479 --- /dev/null +++ "b/fr/09_G\303\251n\303\251rer_des_mipmaps.md" @@ -0,0 +1,410 @@ +## Introduction + +Notre programme peut maintenant charger et afficher des modèles 3D. Dans ce chapitre nous allons ajouter une nouvelle +fonctionnalité : celle de générer et d'utiliser des mipmaps. Elles sont utilisées dans tous les applications 3D. Vulkan +laisse au programmeur un control quasiment total sur leur génération. + +Les mipmaps sont des versions de qualité réduite précalculées d'une texture. Chacune de ces versions est deux fois +moins haute et large que l'originale. Les objets plus distants de la caméra peuvent utiliser ces versions pour le +sampling de la texture. Le rendu est alors plus rapide et plus lisse. Voici un exemple de mipmaps : + +![](/images/mipmaps_example.jpg) + +## Création des images + +Avec Vulkan, chaque niveau de mipmap est stocké dans les différents *niveaux de mipmap* de l'image originale. Le niveau +0 correspond à l'image originale. Les images suivantes sont souvent appelées *mip chain*. + +Le nombre de niveaux de mipmap doit être fourni lors de la création de l'image. Jusqu'à présent nous avons indiqué la +valeur `1`. Nous devons ainsi calculer le nombre de mipmaps à générer à partir de la taille de l'image. Créez un membre +donnée pour contenir cette valeur : + +```c++ +... +uint32_t mipLevels; +VkImage textureImage; +... +``` + +La valeur pour `mipLevels` peut être déterminée une fois que nous avons chargé la texture dans `createTextureImage` : + +```c++ +int texWidth, texHeight, texChannels; +stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); +... +mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + +``` + +La troisième ligne ci-dessus calcule le nombre de niveaux de mipmaps. La fonction `max` chosit la plus grande des +dimensions, bien que dans la pratique les textures seront toujours carrées. Ensuite, `log2` donne le nombre de fois que +les dimensions peuvent être divisées par deux. La fonction `floor` gère le cas où la dimension n'est pas un multiple +de deux (ce qui est déconseillé). `1` est finalement rajouté pour que l'image originale soit aussi comptée. + +Pour utiliser cette valeur nous devons changer les fonctions `createImage`, `createImageView` et +`transitionImageLayout`. Nous devrons y indiquer le nombre de mipmaps. Ajoutez donc cette donnée en paramètre à toutes +ces fonctions : + +```c++ +void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + ... + imageInfo.mipLevels = mipLevels; + ... +} +``` + +```c++ +VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) { + ... + viewInfo.subresourceRange.levelCount = mipLevels; + ... +``` + +```c++ +void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) { + ... + barrier.subresourceRange.levelCount = mipLevels; + ... +``` + +Il nous faut aussi mettre à jour les appels. + +```c++ +createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); +... +createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +``` + +```c++ +swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); +... +depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1); +... +textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels); +``` + +```c++ +transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1); +... +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); +``` + +## Génération des mipmaps + +Notre texture a plusieurs niveaux de mipmaps, mais le buffer intermédiaire ne peut pas gérer cela. Les niveaux +autres que 0 sont indéfinis. Pour les remplir nous devons générer les mipmaps à partir du seul niveau que nous avons. +Nous allons faire cela du côté de la carte graphique. Nous allons pour cela utiliser la commande `vkCmdBlitImage`. +Elle effectue une copie, une mise à l'échelle et un filtrage. Nous allons l'appeler une fois par niveau. + +Cette commande est considérée comme une opération de transfert. Nous devons donc indiquer que la mémoire de l'image sera +utilisée à la fois comme source et comme destination de la commande. Ajoutez `VK_IMAGE_USAGE_TRANSFER_SRC_BIT` à la +création de l'image. + +```c++ +... +createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +... +``` + +Comme pour les autres opérations sur les images, la commande `vkCmdBlitImage` dépend de l'organisation de l'image sur +laquelle elle opère. Nous pourrions transitionner l'image vers `VK_IMAGE_LAYOUT_GENERAL`, mais les opérations +prendraient beaucoup de temps. En fait il est possible de transitionner les niveaux de mipmaps indépendemment les uns +des autres. Nous pouvons donc mettre l'image initiale à `VK_IMAGE_LAYOUT_TRANSFER_SCR_OPTIMAL` et la chaîne de mipmaps +à `VK_IMAGE_LAYOUT_DST_OPTIMAL`. Nous pourrons réaliser les transitions à la fin de chaque opération. + +La fonction `transitionImageLayout` ne peut réaliser une transition d'organisation que sur l'image entière. Nous allons +donc devoir écrire quelque commandes liées aux barrières de pipeline. Supprimez la transition vers +`VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` dans `createTextureImage` : + +```c++ +... +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +//transitionné vers VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL lors de la generation des mipmaps +... +``` + +Tous les niveaux de l'image seront ainsi en `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. Chaque niveau sera ensuite +transitionné vers `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` après l'exécution de la commande. + +Nous allons maintenant écrire la fonction qui génèrera les mipmaps. + +```c++ +void generateMipmaps(VkImage image, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.image = image; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + endSingleTimeCommands(commandBuffer); +} +``` + +Nous allons réaliser plusieurs transitions, et pour cela nous réutiliserons cette structure `VkImageMemoryBarrier`. Les +champs remplis ci-dessus seront valides pour tous les niveaux, et nous allons changer les champs manquant au fur et à +mesure de la génération des mipmaps. + +```c++ +int32_t mipWidth = texWidth; +int32_t mipHeight = texHeight; + +for (uint32_t i = 1; i < mipLevels; i++) { + +} +``` + +Cette boucle va enregistrer toutes les commandes `VkCmdBlitImage`. Remarquez que la boucle commence à 1, et pas à 0. + +```c++ +barrier.subresourceRange.baseMipLevel = i - 1; +barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; +barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; +barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; +barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + +vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); +``` + +Tout d'abord nous transitionnons le `i-1`ième niveau vers `VK_IMAGE_LAYOUT_TRANSFER_SCR_OPTIMAL`. Cette transition +attendra que le niveau de mipmap soit prêt, que ce soit par copie depuis le buffer pour l'image originale, ou bien par +`vkCmdBlitImage`. La commande de génération de la mipmap suivante attendra donc la fin de la précédente. + +```c++ +VkImageBlit blit{}; +blit.srcOffsets[0] = { 0, 0, 0 }; +blit.srcOffsets[1] = { mipWidth, mipHeight, 1 }; +blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +blit.srcSubresource.mipLevel = i - 1; +blit.srcSubresource.baseArrayLayer = 0; +blit.srcSubresource.layerCount = 1; +blit.dstOffsets[0] = { 0, 0, 0 }; +blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; +blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +blit.dstSubresource.mipLevel = i; +blit.dstSubresource.baseArrayLayer = 0; +blit.dstSubresource.layerCount = 1; +``` + +Nous devons maintenant indiquer les régions concernées par la commande. Le niveau de mipmap source est `i-1` et le +niveau destination est `i`. Les deux éléments du tableau `scrOffsets` déterminent en 3D la région source, et +`dstOffsets` la région cible. Les coordonnées X et Y sont à chaque fois divisées par deux pour réduire la taille des +mipmaps. La coordonnée Z doit être mise à la profondeur de l'image, c'est à dire 1. + +```c++ +vkCmdBlitImage(commandBuffer, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blit, + VK_FILTER_LINEAR); +``` + +Nous enregistrons maintenant les commandes. Remarquez que `textureImage` est utilisé à la fois comme source et comme +cible, car la commande s'applique à plusieurs niveaux de l'image. Le niveau de mipmap source vient d'être transitionné +vers `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`, et le niveau cible est resté en destination depuis sa création. + +Attention au cas où vous utilisez une queue de transfert dédiée (comme suggéré dans [Vertex buffers](!fr/Vertex_buffers/Buffer_intermédiaire)) : la fonction `vkCmdBlitImage` doit être envoyée dans une queue graphique. + +Le dernier paramètre permet de fournir un `VkFilter`. Nous voulons le même filtre que pour le sampler, nous pouvons donc +mettre `VK_FILTER_LINEAR`. + +```c++ +barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; +barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; +barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + +vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); +``` + +Ensuite, la boucle transtionne le `i-1`ième niveau de mipmap vers l'organisation optimale pour la lecture par shader. +La transition attendra la fin de la commande, de même que les opérations de sampling. + +```c++ + ... + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 1) mipHeight /= 2; +} +``` + +Les tailles de la mipmap sont ensuite divisées par deux. Nous vérifions quand même que ces dimensions sont bien +supérieures à 1, ce qui peut arriver dans le cas d'une image qui n'est pas carrée. + +```c++ + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + endSingleTimeCommands(commandBuffer); +} +``` + +Avant de terminer avec le command buffer, nous devons ajouter une dernière barrière. Elle transitionne le dernier +niveau de mipmap vers `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. Ce cas n'avait pas été géré par la boucle, car elle +n'a jamais servie de source à une copie. + +Appelez finalement cette fonction depuis `createTextureImage` : + +```c++ +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +//transions vers VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL pendant la génération des mipmaps +... +generateMipmaps(textureImage, texWidth, texHeight, mipLevels); +``` + +Les mipmaps de notre image sont maintenant complètement remplies. + +## Support pour le filtrage linéaire + +La fonction `vkCmdBlitImage` est extrêmement pratique. Malheureusement il n'est pas garanti qu'elle soit disponible. Elle +nécessite que le format de l'image texture supporte ce type de filtrage, ce que nous pouvons vérifier avec la fonction +`vkGetPhysicalDeviceFormatProperties`. Nous allons vérifier sa disponibilité dans `generateMipmaps`. + +Ajoutez d'abord un paramètre qui indique le format de l'image : + +```c++ +void createTextureImage() { + ... + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, mipLevels); +} + +void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + + ... +} +``` + +Utilisez `vkGetPhysicalDeviceFormatProperties` dans `generateMipmaps` pour récupérer les propriétés liés au format : + +```c++ +void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + + // Vérifions si l'image supporte le filtrage linéaire + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties); + + ... +``` + +La structure `VkFormatProperties` possède les trois champs `linearTilingFeatures`, `optimalTilingFeature` et +`bufferFeaetures`. Ils décrivent chacun l'utilisation possible d'images de ce format dans certains contextes. Nous avons +créé l'image avec le format optimal, les informations qui nous concernent sont donc dans `optimalTilingFeatures`. Le +support pour le filtrage linéaire est ensuite indiqué par `VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT`. + +```c++ +if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { + throw std::runtime_error("le format de l'image texture ne supporte pas le filtrage lineaire!"); +} +``` + +Il y a deux alternatives si le format ne permet pas l'utilisation de `vkCmdBlitImage`. Vous pouvez créer une fonction +pour essayer de trouver un format supportant la commande, ou vous pouvez utiliser une librairie pour générer les +mipmaps comme [stb_image_resize](https://github.com/nothings/stb/blob/master/stb_image_resize.h). Chaque niveau de +mipmap peut ensuite être chargé de la même manière que vous avez chargé l'image. + +Souvenez-vous qu'il est rare de générer les mipmaps pendant l'exécution. Elles sont généralement prégénérées et stockées +dans le fichier avec l'image de base. Le chargement de mipmaps prégénérées est laissé comme exercice au lecteur. + +## Sampler + +Un objet `VkImage` contient les données de l'image et un objet `VkSampler` contrôle la lecture des données pendant le +rendu. Vulkan nous permet de spécifier les valeurs `minLod`, `maxLod`, `mipLodBias` et `mipmapMode`, où "Lod" signifie +*level of detail* (*niveau de détail*). Pendant l'échantillonnage d'une texture, le sampler sélectionne le niveau de +mipmap à utiliser suivant ce pseudo-code : + +```c++ +lod = getLodLevelFromScreenSize(); //plus petit quand l'objet est proche, peut être negatif +lod = clamp(lod + mipLodBias, minLod, maxLod); + +level = clamp(floor(lod), 0, texture.mipLevels - 1); //limité par le nombre de niveaux de mipmaps dans le texture + +if (mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST) { + color = sample(level); +} else { + color = blend(sample(level), sample(level + 1)); +} +``` + +Si `samplerInfo.mipmapMode` est `VK_SAMPLER_MIPMAP_MODE_NEAREST`, la variable `lod` correspond au niveau de mipmap à +échantillonner. Sinon, si il vaut `VK_SAMPLER_MIPMAP_MODE_LINEAR`, deux niveaux de mipmaps sont samplés, puis interpolés +linéairement. + +L'opération d'échantillonnage est aussi affectée par `lod` : + +```c++ +if (lod <= 0) { + color = readTexture(uv, magFilter); +} else { + color = readTexture(uv, minFilter); +} +``` + +Si l'objet est proche de la caméra, `magFilter` est utilisé comme filtre. Si l'objet est plus distant, `minFilter` sera +utilisé. Normalement `lod` est positif, est devient nul au niveau de la caméra. `mipLodBias` permet de forcer Vulkan à +utiliser un `lod` plus petit et donc un noveau de mipmap plus élevé. + +Pour voir les résultats de ce chapitre, nous devons choisir les valeurs pour `textureSampler`. Nous avons déjà fourni +`minFilter` et `magFilter`. Il nous reste les valeurs `minLod`, `maxLod`, `mipLodBias` et `mipmapMode`. + +```c++ +void createTextureSampler() { + ... + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = VK_LOD_CLAMP_NONE; + samplerInfo.mipLodBias = 0.0f; // Optionnel + ... +} +``` + +Pour utiliser la totalité des niveaux de mipmaps, nous mettons `minLod` à `0.0f` et `maxLod` à `VK_LOD_CLAMP_NONE`. Cette constante est égale à `1000.0f`, ce qui veut dire que la totalité des niveaux de mipmaps disponible dans la texture sera échantillonée. Nous n'avons aucune raison d'altérer `lod` avec `mipLodBias`, alors nous pouvons le mettre à `0.0f`. + +Lancez votre programme et vous devriez voir ceci : + +![](/images/mipmaps.png) + +Notre scène est si simple qu'il n'y a pas de différence majeure. En comparant précisement on peut voir quelques +différences. + +![](/images/mipmaps_comparison.png) + +La différence la plus évidente est l'écriture sur le paneau, plus lisse avec les mipmaps. + +Vous pouvez modifier les paramètres du sampler pour voir l'impact sur le rendu. Par exemple vous pouvez empêcher le +sampler d'utiliser le plus haut nivau de mipmap en ne lui indiquant pas le niveau le plus bas : + +```c++ +samplerInfo.minLod = static_cast(mipLevels / 2); +``` + +Ce paramètre produira ce rendu : + +![](/images/highmipmaps.png) + +[Code C++](/code/28_mipmapping.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git a/fr/10_Multisampling.md b/fr/10_Multisampling.md new file mode 100644 index 00000000..85926e07 --- /dev/null +++ b/fr/10_Multisampling.md @@ -0,0 +1,323 @@ +## Introduction + +Notre programme peut maintenant générer plusieurs niveaux de détails pour les textures qu'il utilise. Ces images sont +plus lisses quand vues de loin. Cependant on peut voir des motifs en dent de scie si on regarde les textures de plus +près. Ceci est particulièrement visible sur le rendu de carrés : + +![](/images/texcoord_visualization.png) + +Cet effet indésirable s'appelle "aliasing". Il est dû au manque de pixels pour afficher tous les détails de la +géométrie. Il sera toujours visible, par contre nous pouvons utiliser des techniques pour le réduire considérablement. +Nous allons ici implémenter le [multisample anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing), +terme condensé en MSAA. + +Dans un rendu standard, la couleur d'un pixel est déterminée à partir d'un unique sample, en général le centre du pixel. +Si une ligne passe partiellement par un pixel sans en toucher le centre, sa contribution à la couleur sera nulle. Nous +voudrions plutôt qu'il y contribue partiellement. + +![](/images/aliasing.png) + +Le MSAA consiste à utiliser plusieurs points dans un pixel pour déterminer la couleur d'un pixel. Comme on peut s'y +attendre, plus de points offrent un meilleur résultat, mais consomment plus de ressources. + +![](/images/antialiasing.png) + +Nous allons utiliser le maximum de points possible. Si votre application nécessite plus de performances, il vous suffira +de réduire ce nombre. + +## Récupération du nombre maximal de samples + +Commençons par déterminer le nombre maximal de samples que la carte graphique supporte. Les GPUs modernes supportent au +moins 8 points, mais il peut tout de même différer entre modèles. Nous allons stocker ce nombre dans un membre donnée : + +```c++ +... +VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT; +... +``` + +Par défaut nous n'utilisons qu'un point, ce qui correspond à ne pas utiliser de multisampling. Le nombre maximal est +inscrit dans la structure de type `VkPhysicalDeviceProperties` associée au GPU. Comme nous utilisons un buffer de +profondeur, nous devons prendre en compte le nombre de samples pour la couleur et pour la profondeur. Le plus haut taux +de samples supporté par les deux (&) sera celui que nous utiliserons. Créez une fonction dans laquelle les informations +seront récupérées : + +```c++ +VkSampleCountFlagBits getMaxUsableSampleCount() { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); + + VkSampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; } + if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; } + if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; } + if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; } + if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; } + if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; } + + return VK_SAMPLE_COUNT_1_BIT; +} +``` + +Nous allons maintenant utiliser cette fonction pour donner une valeur à `msaaSamples` pendant la sélection du GPU. Nous +devons modifier la fonction `pickPhysicalDevice` : + +```c++ +void pickPhysicalDevice() { + ... + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + msaaSamples = getMaxUsableSampleCount(); + break; + } + } + ... +} +``` + +## Mettre en place une cible de rendu + +Le MSAA consiste à écrire chaque pixel dans un buffer indépendant de l'affichage, dont le contenu est ensuite rendu en +le résolvant à un framebuffer standard. Cette étape est nécessaire car le premier buffer est une image particulière : +elle doit supporter plus d'un échantillon par pixel. Il ne peut pas être utilisé comme framebuffer dans la swap chain. +Nous allons donc devoir changer notre rendu. Nous n'aurons besoin que d'une cible de rendu, car seule une opération +de rendu n'est autorisée à s'exécuter à un instant donné. Créez les membres données suivants : + +```c++ +... +VkImage colorImage; +VkDeviceMemory colorImageMemory; +VkImageView colorImageView; +... +``` + +Cette image doit supporter le nombre de samples déterminé auparavant, nous devons donc le lui fournir durant sa +création. Ajoutez un paramètre `numSamples` à la fonction `createImage` : + +```c++ +void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCountFlagBits numSamples, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + ... + imageInfo.samples = numSamples; + ... +``` + +Mettez à jour tous les appels avec `VK_SAMPLE_COUNT_1_BIT`. Nous changerons cette valeur pour la nouvelle image. + +```c++ +createImage(swapChainExtent.width, swapChainExtent.height, 1, VK_SAMPLE_COUNT_1_BIT, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); +... +createImage(texWidth, texHeight, mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +``` + +Nous allons maintenant créer un buffer de couleur à plusieurs samples. Créez la fonction `createColorResources`, et +passez `msaaSamples` à `createImage` depuis cette fonction. Nous n'utilisons également qu'un niveau de mipmap, ce qui +est nécessaire pour conformer à la spécification de Vulkan. Mais de toute façon cette image n'a pas besoin de mipmaps. + +```c++ +void createColorResources() { + VkFormat colorFormat = swapChainImageFormat; + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, colorImage, colorImageMemory); + colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); +} +``` + +Pour une question de cohérence mettons cette fonction juste avant `createDepthResource`. + +```c++ +void initVulkan() { + ... + createColorResources(); + createDepthResources(); + ... +} +``` + +Nous avons maintenant un buffer de couleurs qui utilise le multisampling. Occupons-nous maintenant de la profondeur. +Modifiez `createDepthResources` et changez le nombre de samples utilisé : + +```c++ +void createDepthResources() { + ... + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); + ... +} +``` + +Comme nous avons créé quelques ressources, nous devons les libérer : + +```c++ +void cleanupSwapChain() { + vkDestroyImageView(device, colorImageView, nullptr); + vkDestroyImage(device, colorImage, nullptr); + vkFreeMemory(device, colorImageMemory, nullptr); + ... +} +``` + +Mettez également à jour `recreateSwapChain` pour prendre en charge les recréations de l'image couleur. + +```c++ +void recreateSwapChain() { + ... + createGraphicsPipeline(); + createColorResources(); + createDepthResources(); + ... +} +``` + +Nous avons fini le paramétrage initial du MSAA. Nous devons maintenant utiliser ces ressources dans la pipeline, le +framebuffer et la render pass! + +## Ajouter de nouveaux attachements + +Gérons d'abord la render pass. Modifiez `createRenderPass` et changez-y la création des attachements de couleur et de +profondeur. + +```c++ +void createRenderPass() { + ... + colorAttachment.samples = msaaSamples; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + ... + depthAttachment.samples = msaaSamples; + ... +``` + +Nous avons changé l'organisation finale à `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`, car les images qui utilisent le +multisampling ne peuvent être présentées directement. Nous devons la convertir en une image plus classique. Nous +n'aurons pas à convertir le buffer de profondeur, dans la mesure où il ne sera jamais présenté. Nous avons donc besoin +d'un nouvel attachement pour la couleur, dans lequel les pixels seront résolus. + +```c++ + ... + VkAttachmentDescription colorAttachmentResolve{}; + colorAttachmentResolve.format = swapChainImageFormat; + colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + ... +``` + +La render pass doit maintenant être configurée pour résoudre l'attachement multisamplé en un attachement simple. +Créez une nouvelle référence au futur attachement qui contiendra le buffer de pixels résolus : + +```c++ + ... + VkAttachmentReference colorAttachmentResolveRef{}; + colorAttachmentResolveRef.attachment = 2; + colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + ... +``` + +Ajoutez la référence à l'attachement dans le membre `pResolveAttachments` de la structure de création de la subpasse. +La subpasse n'a besoin que de cela pour déterminer l'opération de résolution du multisampling : + +``` + ... + subpass.pResolveAttachments = &colorAttachmentResolveRef; + ... +``` + +Fournissez ensuite l'attachement de couleur à la structure de création de la render pass. + +```c++ + ... + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; + ... +``` + +Modifiez ensuite `createFramebuffer` afin de d'ajouter une image view de couleur à la liste : + +```c++ +void createFrameBuffers() { + ... + std::array attachments = { + colorImageView, + depthImageView, + swapChainImageViews[i] + }; + ... +} +``` + +Il ne reste plus qu'à informer la pipeline du nombre de samples à utiliser pour les opérations de rendu. + +```c++ +void createGraphicsPipeline() { + ... + multisampling.rasterizationSamples = msaaSamples; + ... +} +``` + +Lancez votre programme et vous devriez voir ceci : + +![](/images/multisampling.png) + +Comme pour le mipmapping, la différence n'est pas forcément visible immédiatement. En y regardant de plus près, vous +pouvez normalement voir que, par exemple, les bords sont beaucoup plus lisses qu'avant. + +![](/images/multisampling_comparison.png) + +La différence est encore plus visible en zoomant sur un bord : + +![](/images/multisampling_comparison2.png) + +## Amélioration de la qualité + +Notre implémentation du MSAA est limitée, et ces limitations impactent la qualité. Il existe un autre problème +d'aliasing dû aux shaders qui n'est pas résolu par le MSAA. En effet cette technique ne permet que de lisser les bords +de la géométrie, mais pas les lignes contenus dans les textures. Ces bords internes sont particulièrement visibles dans +le cas de couleurs qui contrastent beaucoup. Pour résoudre ce problème nous pouvons activer le +[sample shading](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap27.html#primsrast-sampleshading), qui +améliore encore la qualité de l'image au prix de performances encore réduites. + +```c++ + +void createLogicalDevice() { + ... + deviceFeatures.sampleRateShading = VK_TRUE; // Activation du sample shading pour le device + ... +} + +void createGraphicsPipeline() { + ... + multisampling.sampleShadingEnable = VK_TRUE; // Activation du sample shading dans la pipeline + multisampling.minSampleShading = .2f; // Fraction minimale pour le sample shading; plus proche de 1 lisse d'autant plus + ... +} +``` + +Dans notre tutoriel nous désactiverons le sample shading, mais dans certain cas son activation permet une nette +amélioration de la qualité du rendu : + +![](/images/sample_shading.png) + +## Conclusion + +Il nous a fallu beaucoup de travail pour en arriver là, mais vous avez maintenant une bonne connaissances des bases de +Vulkan. Ces connaissances vous permettent maintenant d'explorer d'autres fonctionnalités, comme : + +* Push constants +* Instanced rendering +* Uniforms dynamiques +* Descripteurs d'images et de samplers séparés +* Pipeline caching +* Génération des command buffers depuis plusieurs threads +* Multiples subpasses +* Compute shaders + +Le programme actuel peut être grandement étendu, par exemple en ajoutant l'éclairage Blinn-Phong, des effets en +post-processing et du shadow mapping. Vous devriez pouvoir apprendre ces techniques depuis des tutoriels conçus pour +d'autres APIs, car la plupart des concepts sont applicables à Vulkan. + +[Code C++](/code/29_multisampling.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git a/fr/90_FAQ.md b/fr/90_FAQ.md new file mode 100644 index 00000000..4aca4904 --- /dev/null +++ b/fr/90_FAQ.md @@ -0,0 +1,20 @@ +Cette page liste quelques problèmes que vous pourriez rencontrer lors du développement d'une application Vulkan. + +* **J'obtiens un erreur de violation d'accès dans les validations layers** : assurez-vous que MSI Afterburner / +RivaTuner Statistics Server ne tournent pas, car ils possèdent des problèmes de compatibilité avec Vulkan. + +* **Je ne vois aucun message provenant des validation layers / les validation layers ne sont pas disponibles** : +assurez-vous d'abord que les validation layers peuvent écrire leurs message en laissant le terminal ouvert après +l'exécution. Avec Visual Studio, lancez le programme avec Ctrl-F5. Sous Linux, lancez le programme depuis un terminal. +S'il n'y a toujours pas de message, revoyez l'installation du SDK en suivant les instructions de [cette page](https://vulkan.lunarg.com/doc/view/1.2.135.0/windows/getting_started.html) (section "Verify the Installation"). +Assurez-vous également que le SDK est au moins de la version 1.1.106.0 pour le support de `VK_LAYER_KHRONOS_validation`. + +* **vkCreateSwapchainKHR induit une erreur dans SteamOverlayVulkanLayer64.dll** : Il semble qu'il y ait un problème de +compatibilité avec la version beta du client Steam. Il y a quelques moyens de régler le conflit : + * Désinstaller Steam + * Mettre la variable d'environnement `DISABLE_VK_LAYER_VALVE_steam_overlay_1` à `1` + * Supprimer la layer de Steam dans le répertoire sous `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers` + +Exemple pour la variable : + +![](/images/steam_layers_env.png) \ No newline at end of file diff --git "a/fr/95_Politique_de_confidentialit\303\251.md" "b/fr/95_Politique_de_confidentialit\303\251.md" new file mode 100644 index 00000000..1317183b --- /dev/null +++ "b/fr/95_Politique_de_confidentialit\303\251.md" @@ -0,0 +1,34 @@ +## Généralités + +Cette politique de confidentialité concerne les informations collectées quand vous utilisez vulkan-tutorial.com ou +l'un de ses sous-domaines. Il décrit la manière dont Alexander Overvoorde, propriétaire du site, collecte, utilise et +partage les informations vous concernant. + +## Renseignements + +Ce site web collecte des informations sur ses visiteurs à l'aide d'une instance Matomo +([https://matomo.org/](https://matomo.org/)) localement hébergée. Il analyse les pages que vous visitez, le type +d'appareil et le navigateur que vous utilisez, combien de temps vous restez sur une page et comment vous êtes arrivés +sur le site. Ces informations sont anonymisées en ne stockant que les deux premiers octets de votre addresse IP (par +exemple `123.123.xxx.xxx`). Ces données sont stockés pour une durée indéterminée. + +Les données sont utilisées dans déterminer trois données : la manière dont le site est utilisé, le nombre de visiteurs +en général et les sites qui mènent à ce site web. Cela permet de mieux engager un contact avec la communauté, et de +déterminer les zones du site à améliorer. Par exemple cela permet de savoir s'il faut investir plus de temps dans +l'interface mobile. + +Ces données ne sont pas partagées à des tiers. + +## Publicité + +Ce site utilise des publicités fournies par des serveurs tiers. Elles peuvent utiliser des cookies pour suivre +l'activité du site ou l'engagement avec les publicités. + +## Commentaires + +Chaque chapitre comporte une section commentaires. Elle utilise le service tier Disqus. Ce service collecte des données +individuelles pour faciliter la lecture et l'émission de commentaires. Il agglomère ces informations pour améliorer son +offre de services. + +La politique de confidentialité complète de ce service tier se trouve à l'addresse suivante : +[https://help.disqus.com/terms-and-policies/disqus-privacy-policy](https://help.disqus.com/terms-and-policies/disqus-privacy-policy). \ No newline at end of file diff --git a/images/aliasing.png b/images/aliasing.png new file mode 100644 index 00000000..cdb3c269 Binary files /dev/null and b/images/aliasing.png differ diff --git a/images/anisotropic_filtering.png b/images/anisotropic_filtering.png new file mode 100644 index 00000000..9c946dda Binary files /dev/null and b/images/anisotropic_filtering.png differ diff --git a/images/antialiasing.png b/images/antialiasing.png new file mode 100644 index 00000000..644f569b Binary files /dev/null and b/images/antialiasing.png differ diff --git a/images/compute_shader_particles.png b/images/compute_shader_particles.png new file mode 100644 index 00000000..139c2469 Binary files /dev/null and b/images/compute_shader_particles.png differ diff --git a/images/compute_space.svg b/images/compute_space.svg new file mode 100644 index 00000000..2f9c1a67 --- /dev/null +++ b/images/compute_space.svg @@ -0,0 +1,4 @@ + + + +
Dispatch
Dispatch
Workgroup
[0,0,2]
Workgroup...
Workgroup
[1,0,2]
Workgroup...
Workgroup
[2,0,2]
Workgroup...
Workgroup
[2,1,2]
Workgroup...
Workgroup
[2,2,2]
Workgroup...
Dispatch
Dispatch
Workgroup
[0,0,1]
Workgroup...
Workgroup
[1,0,1]
Workgroup...
Workgroup
[2,0,1]
Workgroup...
Workgroup
[2,1,1]
Workgroup...
Workgroup
[2,2,1]
Workgroup...
X
X
Y
Y
Workgroup
[0,1,1]
Workgroup...
Workgroup
[1,1,1]
Workgroup...
Workgroup
[0,2,1]
Workgroup...
Workgroup
[1,2,1]
Workgroup...
Dispatch
Dispatch
Workgroup
[0,0,0]
Workgroup...
Workgroup
[1,0,0]
Workgroup...
Workgroup
[2,0,0]
Workgroup...
Workgroup
[0,1,0]
Workgroup...
Workgroup
[1,1,0]
Workgroup...
Workgroup
[2,1,0]
Workgroup...
Workgroup
[0,2,0]
Workgroup...
Workgroup
[1,2,0]
Workgroup...
Workgroup
[2,2,0]
Workgroup...
Z
Z

Invocation
[0,0,0]
Invocation...
Invocation
[1,0,0]
Invocation...
Invocation
[0,1,0]
Invocation...
Invocation
[1,1,0]
Invocation...

Invocation
[0,0,0]
Invocation...
Invocation
[1,0,0]
Invocation...
Invocation
[0,1,0]
Invocation...
Invocation
[1,1,0]
Invocation...
Work group [0,0,0]
Work group [0,0,0]
Invocation
[0,0,0]
Invocation...
Invocation
[1,0,0]
Invocation...
Invocation
[0,1,0]
Invocation...
Invocation
[1,1,0]
Invocation...
Invocation
[0,0,0]
Invocation...
Invocation
[1,0,0]
Invocation...
Invocation
[0,1,0]
Invocation...
Invocation
[1,1,0]
Invocation...
Invocation
[0,0,0]
Invocation...
Invocation
[1,0,0]
Invocation...
Invocation
[0,1,0]
Invocation...
Invocation
[1,1,0]
Invocation...
Invocation
[0,0,0]
Invocation...
Invocation
[1,0,0]
Invocation...
Invocation
[0,1,0]
Invocation...
Invocation
[1,1,0]
Invocation...
Invocation
[0,0,0]
Invocation...
Invocation
[1,0,0]
Invocation...
Invocation
[0,1,0]
Invocation...
Invocation
[1,1,0]
Invocation...
Work group [2,2,0]
Work group [2,2,0]
Invocation
[0,0,0]
Invocation...
Invocation
[1,0,0]
Invocation...
Invocation
[0,1,0]
Invocation...
Invocation
[1,1,0]
Invocation...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/images/compute_ssbo_read_write.svg b/images/compute_ssbo_read_write.svg new file mode 100644 index 00000000..b3a5f507 --- /dev/null +++ b/images/compute_ssbo_read_write.svg @@ -0,0 +1,4 @@ + + + +
Frame 0
DeltaTime = 0.16
Frame 0...
Read
Read
Write
Write
SSBO[0]
Particle[0].Position = 0.16
SSBO[0]...
SSBO[1]
Particle[0].Position = 0.0
SSBO[1]...
Frame 1
DeltaTime = 0.17
Frame 1...
SSBO[1]
Particle[0].Position = 0.33
SSBO[1]...
SSBO[0]
Particle[0].Position = 0.16
SSBO[0]...
Frame 2
DeltaTime = 0.15
Frame 2...
SSBO[0]
Particle[0].Position = 0.48
SSBO[0]...
SSBO[1]
Particle[0].Position = 0.33
SSBO[1]...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/images/cube_demo.png b/images/cube_demo.png new file mode 100644 index 00000000..21e928e7 Binary files /dev/null and b/images/cube_demo.png differ diff --git a/images/cube_demo_mac.png b/images/cube_demo_mac.png new file mode 100644 index 00000000..9a075934 Binary files /dev/null and b/images/cube_demo_mac.png differ diff --git a/images/cube_demo_nowindow.png b/images/cube_demo_nowindow.png new file mode 100644 index 00000000..1783f661 Binary files /dev/null and b/images/cube_demo_nowindow.png differ diff --git a/images/depth_correct.png b/images/depth_correct.png new file mode 100644 index 00000000..d9dce89a Binary files /dev/null and b/images/depth_correct.png differ diff --git a/images/depth_issues.png b/images/depth_issues.png new file mode 100644 index 00000000..ad486c95 Binary files /dev/null and b/images/depth_issues.png differ diff --git a/images/drawing_model.png b/images/drawing_model.png new file mode 100644 index 00000000..c327281f Binary files /dev/null and b/images/drawing_model.png differ diff --git a/images/extra_square.svg b/images/extra_square.svg new file mode 100644 index 00000000..3fa067f2 --- /dev/null +++ b/images/extra_square.svg @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7 + + + + + + + + + + diff --git a/images/favicon.png b/images/favicon.png new file mode 100644 index 00000000..f90ef25e Binary files /dev/null and b/images/favicon.png differ diff --git a/images/glfw_directory.png b/images/glfw_directory.png new file mode 100644 index 00000000..afd450b3 Binary files /dev/null and b/images/glfw_directory.png differ diff --git a/images/highmipmaps.png b/images/highmipmaps.png new file mode 100644 index 00000000..bb6c6dd0 Binary files /dev/null and b/images/highmipmaps.png differ diff --git a/images/include_dirs_stb.png b/images/include_dirs_stb.png new file mode 100644 index 00000000..9035e765 Binary files /dev/null and b/images/include_dirs_stb.png differ diff --git a/images/include_dirs_tinyobjloader.png b/images/include_dirs_tinyobjloader.png new file mode 100644 index 00000000..4e2fbe36 Binary files /dev/null and b/images/include_dirs_tinyobjloader.png differ diff --git a/images/indexed_rectangle.png b/images/indexed_rectangle.png new file mode 100644 index 00000000..2c7498db Binary files /dev/null and b/images/indexed_rectangle.png differ diff --git a/images/inverted_texture_coordinates.png b/images/inverted_texture_coordinates.png new file mode 100644 index 00000000..4d37371e Binary files /dev/null and b/images/inverted_texture_coordinates.png differ diff --git a/images/library_directory.png b/images/library_directory.png new file mode 100644 index 00000000..692349e0 Binary files /dev/null and b/images/library_directory.png differ diff --git a/images/mipmaps.png b/images/mipmaps.png new file mode 100644 index 00000000..c48bea75 Binary files /dev/null and b/images/mipmaps.png differ diff --git a/images/mipmaps_comparison.png b/images/mipmaps_comparison.png new file mode 100644 index 00000000..405edfd1 Binary files /dev/null and b/images/mipmaps_comparison.png differ diff --git a/images/mipmaps_example.jpg b/images/mipmaps_example.jpg new file mode 100644 index 00000000..c44c9ee7 Binary files /dev/null and b/images/mipmaps_example.jpg differ diff --git a/images/multisampling.png b/images/multisampling.png new file mode 100644 index 00000000..3066ea50 Binary files /dev/null and b/images/multisampling.png differ diff --git a/images/multisampling_comparison.png b/images/multisampling_comparison.png new file mode 100644 index 00000000..221f4dc9 Binary files /dev/null and b/images/multisampling_comparison.png differ diff --git a/images/multisampling_comparison2.png b/images/multisampling_comparison2.png new file mode 100644 index 00000000..048ffc18 Binary files /dev/null and b/images/multisampling_comparison2.png differ diff --git a/images/normalized_device_coordinates.svg b/images/normalized_device_coordinates.svg new file mode 100644 index 00000000..970c9f4b --- /dev/null +++ b/images/normalized_device_coordinates.svg @@ -0,0 +1,219 @@ + + + + + + + + + + image/svg+xml + + + + + + + + Framebuffer coordinates + (0, 0) + (1920, 0) + (0, 1080) + (1920, 1080) + + (960, 540) + + Normalized device coordinates + (-1, -1) + (1, -1) + (-1, 1) + (1, 1) + + (0, 0) + + diff --git a/images/sample_shading.png b/images/sample_shading.png new file mode 100644 index 00000000..7a715760 Binary files /dev/null and b/images/sample_shading.png differ diff --git a/images/select_develop_branch.png b/images/select_develop_branch.png new file mode 100644 index 00000000..1312dcdb Binary files /dev/null and b/images/select_develop_branch.png differ diff --git a/images/semaphore_in_use.png b/images/semaphore_in_use.png new file mode 100644 index 00000000..13dd9a40 Binary files /dev/null and b/images/semaphore_in_use.png differ diff --git a/images/spinning_quad.png b/images/spinning_quad.png new file mode 100644 index 00000000..6c779d89 Binary files /dev/null and b/images/spinning_quad.png differ diff --git a/images/steam_layers_env.png b/images/steam_layers_env.png new file mode 100644 index 00000000..c812e019 Binary files /dev/null and b/images/steam_layers_env.png differ diff --git a/images/swap_chain_validation_layer.png b/images/swap_chain_validation_layer.png new file mode 100644 index 00000000..a0002cda Binary files /dev/null and b/images/swap_chain_validation_layer.png differ diff --git a/images/texcoord_visualization.png b/images/texcoord_visualization.png new file mode 100644 index 00000000..c57b7f37 Binary files /dev/null and b/images/texcoord_visualization.png differ diff --git a/images/texture.jpg b/images/texture.jpg new file mode 100644 index 00000000..975e3380 Binary files /dev/null and b/images/texture.jpg differ diff --git a/images/texture_addressing.png b/images/texture_addressing.png new file mode 100644 index 00000000..c148ff3b Binary files /dev/null and b/images/texture_addressing.png differ diff --git a/images/texture_filtering.png b/images/texture_filtering.png new file mode 100644 index 00000000..c10b247d Binary files /dev/null and b/images/texture_filtering.png differ diff --git a/images/texture_on_square.png b/images/texture_on_square.png new file mode 100644 index 00000000..41627be0 Binary files /dev/null and b/images/texture_on_square.png differ diff --git a/images/texture_on_square_colorized.png b/images/texture_on_square_colorized.png new file mode 100644 index 00000000..98d79803 Binary files /dev/null and b/images/texture_on_square_colorized.png differ diff --git a/images/texture_on_square_repeated.png b/images/texture_on_square_repeated.png new file mode 100644 index 00000000..589d2b2a Binary files /dev/null and b/images/texture_on_square_repeated.png differ diff --git a/images/triangle.png b/images/triangle.png new file mode 100644 index 00000000..0d4dc5e4 Binary files /dev/null and b/images/triangle.png differ diff --git a/images/triangle_coordinates.svg b/images/triangle_coordinates.svg new file mode 100644 index 00000000..5a40d46b --- /dev/null +++ b/images/triangle_coordinates.svg @@ -0,0 +1,107 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + v0 + v1 + v2 + + diff --git a/images/triangle_coordinates_colors.png b/images/triangle_coordinates_colors.png new file mode 100644 index 00000000..233f451d Binary files /dev/null and b/images/triangle_coordinates_colors.png differ diff --git a/images/triangle_white.png b/images/triangle_white.png new file mode 100644 index 00000000..61104df0 Binary files /dev/null and b/images/triangle_white.png differ diff --git a/images/validation_layer_anisotropy.png b/images/validation_layer_anisotropy.png new file mode 100644 index 00000000..2979e6b9 Binary files /dev/null and b/images/validation_layer_anisotropy.png differ diff --git a/images/validation_layer_test.png b/images/validation_layer_test.png new file mode 100644 index 00000000..9b733d22 Binary files /dev/null and b/images/validation_layer_test.png differ diff --git a/images/vertex_vs_index.svg b/images/vertex_vs_index.svg new file mode 100644 index 00000000..188e84c3 --- /dev/null +++ b/images/vertex_vs_index.svg @@ -0,0 +1,243 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + Vertex buffer only + Vertex + index buffer + + + v0 + v1 + v2 + v3 + v4 + v5 + v0 + v1 + v2 + v3 + + Indices{0, 1, 2, 2, 3, 0} + + diff --git a/images/viewports_scissors.png b/images/viewports_scissors.png new file mode 100644 index 00000000..6e6b8a2c Binary files /dev/null and b/images/viewports_scissors.png differ diff --git a/images/vs_all_configs.png b/images/vs_all_configs.png new file mode 100644 index 00000000..628c6bae Binary files /dev/null and b/images/vs_all_configs.png differ diff --git a/images/vs_application_settings.png b/images/vs_application_settings.png new file mode 100644 index 00000000..695e1e1a Binary files /dev/null and b/images/vs_application_settings.png differ diff --git a/images/vs_build_mode.png b/images/vs_build_mode.png new file mode 100644 index 00000000..f9f367aa Binary files /dev/null and b/images/vs_build_mode.png differ diff --git a/images/vs_cpp17.png b/images/vs_cpp17.png new file mode 100644 index 00000000..401a2def Binary files /dev/null and b/images/vs_cpp17.png differ diff --git a/images/vs_cpp_general.png b/images/vs_cpp_general.png new file mode 100644 index 00000000..f5937a52 Binary files /dev/null and b/images/vs_cpp_general.png differ diff --git a/images/vs_dependencies.png b/images/vs_dependencies.png new file mode 100644 index 00000000..5bf85b1b Binary files /dev/null and b/images/vs_dependencies.png differ diff --git a/images/vs_export_template.png b/images/vs_export_template.png new file mode 100644 index 00000000..2b359138 Binary files /dev/null and b/images/vs_export_template.png differ diff --git a/images/vs_include_dirs.png b/images/vs_include_dirs.png new file mode 100644 index 00000000..def6bf65 Binary files /dev/null and b/images/vs_include_dirs.png differ diff --git a/images/vs_link_dirs.png b/images/vs_link_dirs.png new file mode 100644 index 00000000..e0c76f79 Binary files /dev/null and b/images/vs_link_dirs.png differ diff --git a/images/vs_link_input.png b/images/vs_link_input.png new file mode 100644 index 00000000..bcbde435 Binary files /dev/null and b/images/vs_link_input.png differ diff --git a/images/vs_link_settings.png b/images/vs_link_settings.png new file mode 100644 index 00000000..ca187c59 Binary files /dev/null and b/images/vs_link_settings.png differ diff --git a/images/vs_new_cpp_project.png b/images/vs_new_cpp_project.png new file mode 100644 index 00000000..338cd323 Binary files /dev/null and b/images/vs_new_cpp_project.png differ diff --git a/images/vs_new_item.png b/images/vs_new_item.png new file mode 100644 index 00000000..75b4a426 Binary files /dev/null and b/images/vs_new_item.png differ diff --git a/images/vs_new_source_file.png b/images/vs_new_source_file.png new file mode 100644 index 00000000..43717261 Binary files /dev/null and b/images/vs_new_source_file.png differ diff --git a/images/vs_open_project_properties.png b/images/vs_open_project_properties.png new file mode 100644 index 00000000..552d659f Binary files /dev/null and b/images/vs_open_project_properties.png differ diff --git a/images/vs_template.png b/images/vs_template.png new file mode 100644 index 00000000..99601531 Binary files /dev/null and b/images/vs_template.png differ diff --git a/images/vs_test_window.png b/images/vs_test_window.png new file mode 100644 index 00000000..ded64e10 Binary files /dev/null and b/images/vs_test_window.png differ diff --git a/images/vulkan_pipeline_block_diagram.png b/images/vulkan_pipeline_block_diagram.png new file mode 100644 index 00000000..10955e14 Binary files /dev/null and b/images/vulkan_pipeline_block_diagram.png differ diff --git a/images/vulkan_sdk_download_buttons.png b/images/vulkan_sdk_download_buttons.png new file mode 100644 index 00000000..d4b5c76c Binary files /dev/null and b/images/vulkan_sdk_download_buttons.png differ diff --git a/images/vulkan_simplified_pipeline.svg b/images/vulkan_simplified_pipeline.svg new file mode 100644 index 00000000..ac26767b --- /dev/null +++ b/images/vulkan_simplified_pipeline.svg @@ -0,0 +1,1566 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Input assembler + + + Input assembler + + + + + + + + + + Vertex shader + + + Vertex shader + + + + + + + + + + Tessellation + + + Tessellation + + + + + + + + + + Geometry shader + + + Geometry shader + + + + + + + + + + Rasterization + + + Rasterization + + + + + + + + + + Fragment shader + + + Fragment shader + + + + + + + + + + Color blending + + + Color blending + + + + + + + + + Vertex/index buffer + + + Vertex/index buffer + + + + + + + Framebuffer + + + Framebuffer + + + + + + + + + + + 0 + 1 + 2 + + + + + 0 + 1 + 2 + + + + + + + + 0 + 1 + 2 + + + + + + + + + + + 0 + 1 + 2 + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/xcode_frameworks.png b/images/xcode_frameworks.png new file mode 100644 index 00000000..1c05b281 Binary files /dev/null and b/images/xcode_frameworks.png differ diff --git a/images/xcode_new_project.png b/images/xcode_new_project.png new file mode 100644 index 00000000..6566e2e6 Binary files /dev/null and b/images/xcode_new_project.png differ diff --git a/images/xcode_new_project_2.png b/images/xcode_new_project_2.png new file mode 100644 index 00000000..417addb0 Binary files /dev/null and b/images/xcode_new_project_2.png differ diff --git a/images/xcode_output.png b/images/xcode_output.png new file mode 100644 index 00000000..520a51de Binary files /dev/null and b/images/xcode_output.png differ diff --git a/images/xcode_paths.png b/images/xcode_paths.png new file mode 100644 index 00000000..fb29a7b6 Binary files /dev/null and b/images/xcode_paths.png differ diff --git a/images/xcode_variables.png b/images/xcode_variables.png new file mode 100644 index 00000000..3d57b7d8 Binary files /dev/null and b/images/xcode_variables.png differ diff --git a/index.html b/index.html deleted file mode 100644 index 7a5e2994..00000000 --- a/index.html +++ /dev/null @@ -1,276 +0,0 @@ - - - - Vulkan Tutorial - - - - - - - - - - - -
- Vulkan logo -
- -
- - -

Overview

- - Vulkan is a new open standard API by Khronos that offers low-level - control of GPUs for graphics and general purpose computation. It has - been designed from the ground up around the capabilities of modern - hardware. - -
    -
  • -

    Direct GPU control with minimal driver overhead

    - -

    - For example, data is written directly to GPU memory - instead of using calls equivalent to glUniform. - Applications can implement their own memory allocation - strategies. (Source) -

    - -

    - Another feature is the render pass, which - offers control over loading of render targets at the - start and end of renders, which is very useful for - tiling architectures. -

    -
  • -
  • -

    Multi-threading friendly architecture

    - -

    - Multiple threads can create and populate command buffers - at the same time, which can then be submitted to the GPU - by a separate thread. -

    -
  • -
  • -

    Unified API for desktop, mobile and embedded platforms

    - -

    - There is no equivalent of OpenGL ES, Vulkan offers the - same API on all platforms. -

    -
  • -
  • -

    Intermediate bytecode for shaders

    - -

    - The driver accepts shaders in the SPIR-V bytecode - format. Khronos will supply a separate compiler for GLSL - that targets this intermediate format. Third-party - developers will be able to write their own compilers for - other languages. -

    -
  • -
- - All of these features lead to simpler drivers, more predictable - performance, more control and less differences between vendor - implementations. (Source) - -

API

- -

- The official specification isn't going to be released until - later this year, but some details can be gathered from various - sources. -

- -

- The author of this website has figured out how to implement - Hello Triangle using Mantle, which Vulkan is based on. You can - find more information about it here. -

- - The blog post linked above offers some insight. API calls have the - vk prefix and functions take the state that they will - change as their first parameter. - -
-    vkCmdBindDescriptorSet(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, textureDescriptorSet[0], 0);
-    vkQueueSubmit(graphicsQueue, 1, &cmdBuffer, 0, 0, fence);
-    vkMapMemory(staticUniformBufferMemory, 0, (void **)&data);
-    // ...
-    vkUnmapMemory(staticUniformBufferMemory);
-            
- -

- All hardware that supports OpenGL 4.1 / OpenGL ES 3.1 or - later can also support Vulkan. (Source) -

- -

- A talk by Khronos on March 5 has offered some more insight into - the API. The Vulkan API still uses no special types, instead - settling for stdint.h types. The closest thing to - the concept of a context is now a command buffer. - Synchronization is done using events and allows for fences - and resource barriers. The full presentation can be watched - below. -

- -

- -

- -

- The slides are also available. -

- -

- There was also a presentation by Valve earlier that day offering an overview. -

- -

- AMD released the programming guide and API reference for Mantle - around March 18, which has heavily influenced the design of - Vulkan. -

- -

- On August 2015, Khronos released new slides about Vulkan; the main announcements are: Google will support Vulkan on the next version of Android, - Vulkan will support "feature sets" to group together features under a single name, and Vulkan Conformance Tests will be developed together with - Google and the Android Open Source Project (AOSP) and they will be open source. -
More details here - and here. -

- -

- Piers Daniell of NVIDIA gave a presentation titled "Vulkan on NVIDIA GPUs" at SIGGRAPH 2015. He presented an overview of the Vulkan API - and showed some Vulkan demos running on NVIDIA desktop and mobile hardware. He also announced that the NVIDIA Vulkan driver will work on - Windows versions from XP through 10, Linux+SteamOS and Android; the NVIDIA architectures that will support Vulkan will be Fermi (GeForce 400 and 500), - Kepler (GeForce 600 and 700, Tegra K1) and Maxwell (GeForce 900, TITAN X, Tegra X1). -

- -

- -

- -

Slides and Presentations

- -

Khronos Press Briefing SIGGRAPH 2015

-

- During SIGGRAPH 2015, Khronos showed a press briefing getting into details on supported operating systems, validation layers (for debugging), the - Vulkan ecosystem and window system integration. The slides also talk about SPIR-V.
-
- Khronos Press Briefing SIGGRAPH 2015
-

- -

An Overview of Next-Generation Graphics APis

-

- Event hosted during SIGGRAPH 2015 showing off next generation graphics APIs, including details on the Vulkan API.

- Source
-

- -

A Whirlwind Tour of Vulkan

-

- Graham Sellers (AMD) gets into the details of Vulkan. Lots of API information in here.

- Source (PowerPoint) -

- -

Porting Source 2 to Vulkan

-

- Dan Ginsburg (Valve) on porting the Source 2 engine to Vulkan.

- Source (PowerPoint) -

- -

Next-Generation Graphics APIs: Similarities and Differences

-

- Tim Foley (NVIDIA Research) on the differences and similarities between next generation APIs.

- Source (PowerPoint) -

- -

SPIR-V

- -

- Khronos has published a whitepaper that provides an - introduction to the SPIR-V intermediate language. -

- -

- The provisional specification is also available. -

- -

Drivers

- -

- Valve has been working on open source Linux drivers for Intel GPUs (Source). -

- -

Tools

- - Valve, LunarG, Codeplay and other parties are already developing - tools. Displayed below is an introduction video of a Vulkan - debugger being developed by Valve and LunarG. - -

- -

- -

- -

- -

- Khronos has announced that these utilities will be available at - the same time as the first drivers. (Source) -

- -

- LunarG also states that these tools and the first drivers will - be available to developers in 2015. (Source) -

- -

Demos

- - Imagination Technologies has - developed a proof-of-concept driver for Vulkan for their PowerVR - GPUs. They have ported an OpenGL ES 3.0 demo to Vulkan. - -

- -

- - Valve has developed a Vulkan driver for Linux that targets Intel - GPUs. (Source) They've shown Dota 2 running with this driver. - -

- -

- - On August 10, 2015 Imagination Technologies released a new demo comparing Vulkan and OpenGL ES. - On their blog - they provided a rough technical explanation about the performance advantages in using Vulkan. - -

- -

-
- - Fork me on GitHub - - - - diff --git a/make_parser.py b/make_parser.py new file mode 100644 index 00000000..8d0c5b5b --- /dev/null +++ b/make_parser.py @@ -0,0 +1,37 @@ +import argparse + +def make_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Build the pdf and epub files of the Vulkan Tutorial." + ) + + parser.add_argument( + "--geometry:left", + type=str, + required=False, + default="2.5cm", + help="Specify left margin space as a string. Example: 2cm.", + ) + parser.add_argument( + "--geometry:right", + type=str, + required=False, + default="2.5cm", + help="Specify right margin space as a string. Example: 2cm.", + ) + parser.add_argument( + "--geometry:top", + type=str, + required=False, + default="2.5cm", + help="Specify top margin space as a string. Example: 2cm.", + ) + parser.add_argument( + "--geometry:bottom", + type=str, + required=False, + default="2.5cm", + help="Specify bottom margin space as a string. Example: 2cm.", + ) + + return parser diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..7749678c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "vulkantutorial" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = "~=3.13.0" +dependencies = [] diff --git a/resources/viking_room.obj b/resources/viking_room.obj new file mode 100644 index 00000000..7cc74b1f --- /dev/null +++ b/resources/viking_room.obj @@ -0,0 +1,16053 @@ +# Blender v2.82 (sub 7) OBJ File: '' +# www.blender.org +mtllib viking_room.mtl +o mesh_all1_Texture1_0 +v -0.573651 0.001530 0.713748 +v -0.573651 0.151382 -0.000154 +v -0.573651 0.164474 0.619081 +v -0.573651 -0.172251 0.629386 +v -0.573651 -0.158077 -0.000154 +v -0.321455 -0.083704 0.292965 +v -0.330610 -0.076846 0.325114 +v -0.321455 -0.102605 0.286187 +v -0.321455 -0.139709 0.314261 +v -0.321455 -0.133254 0.300642 +v -0.330610 -0.116841 0.347841 +v -0.353414 -0.083704 0.292965 +v -0.353414 -0.102605 0.286187 +v -0.344259 -0.076846 0.325114 +v -0.353414 -0.139709 0.314261 +v -0.344259 -0.116841 0.347841 +v -0.353414 -0.133254 0.300642 +v -0.337435 -0.055125 0.252007 +v -0.343555 -0.059984 0.266330 +v -0.331314 -0.059984 0.266330 +v -0.343555 -0.162781 0.326087 +v -0.337435 -0.176479 0.329128 +v -0.331314 -0.162781 0.326087 +v -0.380872 -0.014709 0.191148 +v -0.386786 0.043401 0.190067 +v -0.383829 0.016847 0.165599 +v -0.276384 -0.008655 0.191148 +v -0.279157 0.016669 0.165599 +v -0.281929 0.036991 0.190067 +v 0.532522 0.273765 0.008346 +v 0.540774 0.354737 0.031466 +v 0.467002 0.246589 0.009207 +v 0.595516 0.256354 0.008721 +v 0.690958 0.677146 -0.003071 +v 0.607800 0.646006 -0.002071 +v 0.638479 0.592487 0.021070 +v 0.253268 0.306429 0.007915 +v 0.298805 0.266359 0.008946 +v 0.268356 0.370791 0.034153 +v 0.184580 0.289839 0.008489 +v 0.221447 0.729652 -0.003731 +v 0.232895 0.642290 0.014291 +v 0.309542 0.688101 -0.002732 +v 0.741905 -0.721043 0.001923 +v 0.319177 -0.223316 0.001923 +v 0.319177 -0.721043 0.001923 +v -0.114645 -0.721043 0.001923 +v -0.114645 -0.223316 0.001923 +v -0.575491 -0.721043 0.001923 +v 0.741905 0.739535 0.001923 +v 0.319177 0.739535 0.001923 +v 0.319177 0.188385 0.001923 +v -0.575491 0.739535 0.001923 +v -0.114645 0.188385 0.001923 +v -0.114645 0.739535 0.001923 +v -0.411461 0.212408 0.675560 +v -0.411461 0.209834 0.750638 +v -0.411461 0.170319 0.699099 +v -0.411461 -0.213822 0.675616 +v -0.411461 -0.173571 0.699646 +v -0.411461 -0.212562 0.745240 +v -0.454846 -0.024046 0.486233 +v -0.445048 -0.025567 0.484215 +v -0.448282 -0.023882 0.507913 +v -0.452934 0.011287 0.485221 +v -0.446340 0.012005 0.506886 +v -0.442980 0.012645 0.483121 +v -0.465520 0.007524 0.534670 +v -0.461744 0.007451 0.548021 +v -0.452587 0.009780 0.528738 +v -0.466905 -0.018078 0.535403 +v -0.454235 -0.020678 0.529610 +v -0.463093 -0.017464 0.548734 +v -0.484932 0.004695 0.560385 +v -0.486844 0.005319 0.573640 +v -0.473494 0.005911 0.563596 +v -0.485945 -0.014022 0.560921 +v -0.474635 -0.015181 0.564200 +v -0.487893 -0.014072 0.574195 +v -0.520481 0.004809 0.560873 +v -0.531981 0.006112 0.564167 +v -0.517902 0.005407 0.574299 +v -0.521493 -0.013890 0.561408 +v -0.518949 -0.013936 0.574853 +v -0.533123 -0.014989 0.564771 +v -0.540070 0.007789 0.535300 +v -0.552785 0.010139 0.529545 +v -0.543663 0.007734 0.548737 +v -0.541457 -0.017846 0.536034 +v -0.545012 -0.017201 0.549451 +v -0.554436 -0.020371 0.530419 +v -0.552724 0.011631 0.485913 +v -0.562516 0.013054 0.483901 +v -0.559070 0.012401 0.507808 +v -0.554638 -0.023732 0.486925 +v -0.561014 -0.023528 0.508837 +v -0.564585 -0.025189 0.484996 +v -0.519142 -0.071106 0.487602 +v -0.484330 -0.070382 0.487579 +v -0.549653 -0.054333 0.487078 +v -0.521847 0.058768 0.483541 +v -0.456523 0.042720 0.484043 +v -0.487034 0.059493 0.483518 +v -0.373970 0.352886 0.561430 +v -0.372429 0.349968 0.556986 +v -0.370280 0.350637 0.551648 +v -0.376749 0.448804 0.544444 +v -0.395875 0.438057 0.585830 +v -0.385656 0.358403 0.551520 +v -0.409958 0.416769 0.618384 +v -0.424311 0.368196 0.639710 +v -0.416400 0.342731 0.626770 +v -0.418078 0.308704 0.626391 +v -0.403129 0.274433 0.585444 +v -0.384375 0.266441 0.538006 +v -0.373184 0.283125 0.503630 +v -0.358251 0.303948 0.477395 +v -0.358985 0.343152 0.458250 +v -0.351293 0.400130 0.470936 +v -0.361022 0.435885 0.499096 +v -0.392141 -0.274407 0.458736 +v -0.404246 -0.280731 0.449455 +v -0.381403 -0.360558 0.500104 +v -0.418947 -0.286666 0.449135 +v -0.432304 -0.290620 0.457863 +v -0.440738 -0.291535 0.473300 +v -0.441989 -0.289165 0.491309 +v -0.441989 -0.289165 0.491309 +v -0.435722 -0.284146 0.507065 +v -0.423617 -0.277822 0.516346 +v -0.408916 -0.271888 0.516665 +v -0.395559 -0.267933 0.507937 +v -0.387125 -0.267019 0.492501 +v -0.385874 -0.269388 0.474492 +v 0.245786 -0.579001 0.152919 +v 0.243482 -0.544751 0.144899 +v 0.222300 -0.596550 0.152919 +v 0.184260 -0.556156 0.144899 +v 0.161260 -0.334844 0.144899 +v 0.216432 -0.328119 0.144899 +v 0.183450 -0.306128 0.150074 +v 0.212467 -0.309309 0.150074 +v 0.188087 -0.593086 0.152919 +v 0.156860 -0.315354 0.150074 +v 0.119065 -0.584458 0.160294 +v 0.151411 -0.590904 0.160294 +v 0.116544 -0.560137 0.149178 +v 0.184027 -0.554069 0.149178 +v 0.186388 -0.578329 0.160294 +v 0.093980 -0.351118 0.149178 +v 0.117136 -0.306193 0.156351 +v 0.087578 -0.319203 0.156351 +v 0.161651 -0.336739 0.149178 +v 0.154635 -0.305797 0.156351 +v 0.118307 -0.589580 0.152700 +v 0.118340 -0.572183 0.144772 +v 0.086177 -0.601870 0.152700 +v 0.055611 -0.565524 0.144772 +v 0.037224 -0.360889 0.144772 +v 0.093361 -0.344161 0.144772 +v 0.058206 -0.330401 0.149888 +v 0.089083 -0.325584 0.149888 +v 0.063742 -0.588814 0.152700 +v 0.034755 -0.344658 0.149888 +v 0.125535 -0.399860 0.162553 +v 0.131949 -0.397949 0.159852 +v 0.129966 -0.404947 0.159384 +v 0.124083 -0.406701 0.163903 +v 0.129043 -0.411630 0.162553 +v 0.135457 -0.409718 0.159852 +v 0.136909 -0.402877 0.158502 +v 0.138796 -0.395907 0.183367 +v 0.132383 -0.397819 0.186068 +v 0.137874 -0.402590 0.186536 +v 0.130931 -0.404660 0.187418 +v 0.135891 -0.409589 0.186068 +v 0.142305 -0.407677 0.183367 +v 0.143757 -0.400836 0.182017 +v 0.139085 -0.418762 0.163774 +v 0.141638 -0.421979 0.158861 +v 0.135908 -0.423221 0.160722 +v 0.134111 -0.420677 0.166742 +v 0.131691 -0.425809 0.164797 +v 0.134244 -0.429025 0.159885 +v 0.139218 -0.427110 0.156916 +v 0.151422 -0.430679 0.173179 +v 0.148869 -0.427463 0.178092 +v 0.147205 -0.433267 0.177255 +v 0.143895 -0.429378 0.181060 +v 0.141474 -0.434509 0.179115 +v 0.144028 -0.437726 0.174202 +v 0.149001 -0.435811 0.171234 +v 0.114156 -0.496712 0.181759 +v 0.113028 -0.507623 0.181759 +v 0.116767 -0.502496 0.145224 +v 0.119946 -0.516136 0.181759 +v 0.130857 -0.517264 0.181759 +v 0.125729 -0.513526 0.145224 +v 0.139370 -0.510347 0.181759 +v 0.140497 -0.499435 0.181759 +v 0.136759 -0.504563 0.145224 +v -0.238488 0.573899 0.581732 +v -0.205640 0.590027 0.581732 +v -0.251197 0.630347 0.559399 +v -0.188545 0.622382 0.581732 +v -0.193733 0.658606 0.581732 +v -0.219222 0.684863 0.581732 +v -0.255276 0.691123 0.581732 +v -0.288124 0.674996 0.581732 +v -0.305219 0.642641 0.581732 +v -0.300031 0.606416 0.581732 +v -0.274543 0.580160 0.581732 +v -0.198390 -0.583185 0.581732 +v -0.190495 -0.547453 0.581732 +v -0.250429 -0.557892 0.559399 +v -0.205110 -0.513905 0.581732 +v -0.236653 -0.495354 0.581732 +v -0.273076 -0.498887 0.581732 +v -0.300466 -0.523154 0.581732 +v -0.308362 -0.558886 0.581732 +v -0.293747 -0.592434 0.581732 +v -0.262203 -0.610985 0.581732 +v -0.225780 -0.607452 0.581732 +v -0.389896 -0.547006 0.921101 +v -0.445245 -0.552738 0.921101 +v -0.334546 -0.552738 0.921101 +v -0.389896 0.547006 0.921101 +v -0.334546 0.552738 0.921101 +v -0.445245 0.552738 0.921101 +v 0.564855 -0.547006 0.921101 +v 0.509506 -0.552738 0.921101 +v 0.620205 -0.552738 0.921101 +v -0.291320 -0.536528 0.566384 +v -0.302021 -0.514091 0.566384 +v -0.387725 -0.513312 0.566384 +v -0.488464 -0.540873 0.566384 +v -0.476036 -0.514259 0.566384 +v -0.387725 -0.513312 0.429362 +v -0.488464 -0.540873 0.429362 +v -0.476036 -0.514259 0.429362 +v -0.291320 -0.536528 0.429362 +v -0.302021 -0.514091 0.429362 +v 0.661798 -0.568567 0.566384 +v 0.651097 -0.546131 0.566384 +v 0.565393 -0.545352 0.566384 +v 0.464654 -0.572912 0.566384 +v 0.477082 -0.546299 0.566384 +v 0.565393 -0.545352 0.429362 +v 0.464654 -0.572912 0.429362 +v 0.477082 -0.546299 0.429362 +v 0.661798 -0.568567 0.429362 +v 0.651097 -0.546131 0.429362 +v -0.291027 0.536528 0.566384 +v -0.387432 0.513312 0.566384 +v -0.301727 0.514091 0.566384 +v -0.488170 0.540872 0.566384 +v -0.475743 0.514259 0.566384 +v -0.387432 0.513312 0.429362 +v -0.475743 0.514259 0.429362 +v -0.488171 0.540872 0.429362 +v -0.291027 0.536528 0.429362 +v -0.301727 0.514091 0.429362 +v -0.453264 -0.438596 0.414817 +v -0.427770 -0.438596 0.394532 +v -0.453264 -0.438596 0.394532 +v -0.453264 -0.464081 0.414817 +v -0.453264 -0.464081 0.394532 +v -0.427770 -0.464081 0.394532 +v -0.453264 -0.257355 0.414817 +v -0.427770 -0.257355 0.394532 +v -0.453264 -0.257355 0.394532 +v -0.453264 -0.282839 0.414817 +v -0.453264 -0.282839 0.394532 +v -0.427770 -0.282839 0.394532 +v -0.453264 0.277542 0.413165 +v -0.427770 0.277542 0.396429 +v -0.453264 0.277542 0.396429 +v -0.453264 0.250185 0.413165 +v -0.453264 0.250185 0.396429 +v -0.427770 0.250185 0.396429 +v -0.453264 0.462431 0.413165 +v -0.427770 0.462431 0.396429 +v -0.453264 0.462431 0.396429 +v -0.453264 0.435082 0.413165 +v -0.453264 0.435082 0.396429 +v -0.427770 0.435082 0.396429 +v -0.274652 0.143427 0.219052 +v -0.265515 0.151991 0.221150 +v -0.263536 0.164246 0.225535 +v -0.293859 0.179881 0.235216 +v -0.302996 0.171317 0.233118 +v -0.304974 0.159062 0.228733 +v -0.260761 -0.130895 0.223100 +v -0.273335 -0.131040 0.225208 +v -0.283112 -0.138774 0.229610 +v -0.271260 -0.170916 0.239332 +v -0.258686 -0.170771 0.237225 +v -0.248910 -0.163037 0.232822 +v 0.017080 0.043178 0.169198 +v 0.010006 -0.015389 0.171680 +v 0.103749 0.003803 0.189413 +v 0.038739 -0.066924 0.173863 +v 0.092303 -0.091744 0.174915 +v 0.150239 -0.080368 0.174433 +v 0.179960 -0.032204 0.174535 +v 0.186180 0.019296 0.172353 +v 0.160915 0.064613 0.170433 +v 0.113813 0.086438 0.169508 +v 0.062868 0.076435 0.169932 +v -0.217900 -0.441431 0.076654 +v -0.207289 -0.436671 0.059471 +v -0.198346 -0.434496 0.078506 +v -0.243401 -0.350576 0.137634 +v -0.248037 -0.369666 0.142316 +v -0.228483 -0.362730 0.144168 +v -0.260360 0.646583 0.674025 +v -0.253966 0.636373 0.698247 +v -0.252678 0.633151 0.670918 +v -0.259357 0.647161 0.674006 +v -0.251673 0.633730 0.670898 +v -0.253030 0.636913 0.698229 +v -0.237613 -0.560210 0.645912 +v -0.247500 -0.557118 0.665355 +v -0.249555 -0.556435 0.643025 +v -0.237897 -0.561109 0.645912 +v -0.249840 -0.557335 0.643025 +v -0.247765 -0.557957 0.665355 +v -0.281383 0.156884 0.264412 +v -0.281430 0.156974 0.280989 +v -0.287747 0.168617 0.270807 +v -0.280328 0.157457 0.264412 +v -0.286694 0.169188 0.270807 +v -0.280379 0.157545 0.280989 +v -0.266766 -0.146384 0.254597 +v -0.266731 -0.146448 0.266520 +v -0.262097 -0.154772 0.259196 +v -0.267520 -0.146803 0.254597 +v -0.262850 -0.155191 0.259196 +v -0.267483 -0.146866 0.266520 +v -0.447121 0.001530 0.713748 +v -0.447121 -0.172251 0.629386 +v -0.573651 -0.172251 0.629386 +v -0.573651 0.001530 0.713748 +v -0.447121 0.164474 0.619081 +v -0.573651 0.164474 0.619081 +v -0.447121 0.151382 -0.000154 +v -0.573651 0.151382 -0.000154 +v -0.447121 -0.158077 -0.000154 +v -0.573651 -0.158077 -0.000154 +v -0.447121 -0.600028 0.004394 +v -0.447121 -0.158077 -0.000154 +v -0.447121 -0.172251 0.629386 +v -0.447121 -0.586933 0.648033 +v -0.447121 0.164474 0.619081 +v -0.447121 0.151382 -0.000154 +v -0.447121 0.593027 -0.005760 +v -0.447121 0.584216 0.641591 +v -0.447121 0.151382 -0.000154 +v -0.447121 -0.158077 -0.000154 +v -0.447121 0.461288 0.800080 +v -0.447121 0.353390 0.857197 +v -0.447121 0.001530 0.713748 +v -0.447121 0.002412 0.864522 +v -0.447121 -0.357613 0.859314 +v -0.447121 -0.443691 0.819132 +v 0.536407 -0.664843 0.002715 +v -0.337867 -0.664843 0.000860 +v -0.338482 -0.664843 0.813595 +v 0.535793 -0.664843 0.815448 +v -0.330770 -0.616018 0.246266 +v -0.330770 -0.616018 0.316683 +v -0.187438 -0.616327 0.314538 +v -0.185947 -0.618911 0.264762 +v -0.335768 -0.681060 0.316683 +v -0.192972 -0.688336 0.314538 +v -0.335768 -0.681060 0.246266 +v -0.191091 -0.685842 0.264762 +v 0.073168 -0.692533 0.299157 +v 0.079190 -0.614170 0.299157 +v 0.075552 -0.615534 0.248478 +v 0.069755 -0.690982 0.248478 +v 0.359678 -0.611262 0.314656 +v 0.352998 -0.698193 0.314656 +v 0.502730 -0.695377 0.322434 +v 0.510377 -0.608245 0.322434 +v 0.349125 -0.610981 0.262758 +v 0.510377 -0.608245 0.243607 +v 0.502730 -0.695377 0.243607 +v 0.342445 -0.697912 0.262758 +v 0.473809 -0.596288 0.851846 +v 0.293424 -0.596288 0.831581 +v 0.293424 -0.686392 0.831581 +v 0.473809 -0.686392 0.851846 +v 0.473809 -0.596288 0.768415 +v 0.473809 -0.686392 0.768415 +v 0.293424 -0.686392 0.748150 +v 0.293424 -0.596288 0.748150 +v 0.473809 -0.596288 0.768415 +v 0.293424 -0.596288 0.748150 +v 0.058102 -0.596288 0.851846 +v 0.058102 -0.686392 0.851846 +v 0.058102 -0.686392 0.768415 +v 0.058102 -0.596288 0.768415 +v 0.058102 -0.596288 0.768415 +v -0.157246 -0.596288 0.836647 +v -0.157246 -0.686392 0.836647 +v -0.157246 -0.686392 0.753216 +v -0.157246 -0.596288 0.753216 +v -0.157246 -0.596288 0.753216 +v -0.300480 -0.596288 0.851846 +v -0.300480 -0.686392 0.851846 +v -0.300480 -0.686392 0.768415 +v -0.300480 -0.596288 0.768415 +v -0.300480 -0.596288 0.768415 +v -0.347241 -0.057963 0.386481 +v -0.343686 -0.073398 0.365938 +v -0.349514 -0.082855 0.371622 +v -0.356623 -0.073770 0.394205 +v -0.356623 -0.073770 0.394205 +v -0.349514 -0.082855 0.371622 +v -0.343686 -0.092311 0.377306 +v -0.347241 -0.089578 0.401930 +v -0.332030 -0.092311 0.377306 +v -0.328476 -0.089578 0.401930 +v -0.326202 -0.082855 0.371622 +v -0.319093 -0.073770 0.394205 +v -0.332030 -0.073398 0.365938 +v -0.328476 -0.057963 0.386481 +v -0.356623 -0.073770 0.394205 +v -0.347241 -0.089578 0.401930 +v -0.328476 -0.089578 0.401930 +v -0.319093 -0.073770 0.394205 +v -0.328476 -0.057963 0.386481 +v -0.347241 -0.057963 0.386481 +v -0.337435 -0.085052 0.239781 +v -0.331314 -0.082573 0.257433 +v -0.331314 -0.110023 0.251556 +v -0.337435 -0.116048 0.234843 +v -0.337435 -0.158964 0.259908 +v -0.331314 -0.148351 0.276723 +v -0.331314 -0.159419 0.302278 +v -0.337435 -0.173274 0.293854 +v -0.331314 -0.131040 0.263252 +v -0.337435 -0.140354 0.244318 +v -0.321455 -0.117789 0.290185 +v -0.330610 -0.096586 0.336106 +v -0.324990 -0.073597 0.275141 +v -0.324990 -0.143295 0.321760 +v -0.343555 -0.110023 0.251556 +v -0.343555 -0.082573 0.257433 +v -0.343555 -0.159419 0.302278 +v -0.343555 -0.148351 0.276723 +v -0.343555 -0.131040 0.263252 +v -0.353414 -0.117789 0.290185 +v -0.344259 -0.096586 0.336106 +v -0.343555 -0.059984 0.266330 +v -0.343555 -0.162781 0.326087 +v -0.349879 -0.073597 0.275141 +v -0.349879 -0.143295 0.321760 +v -0.353414 -0.083704 0.292965 +v -0.344259 -0.076846 0.325114 +v -0.344259 -0.116841 0.347841 +v -0.353414 -0.139709 0.314261 +v -0.349879 -0.073597 0.275141 +v -0.349879 -0.143295 0.321760 +v -0.357733 0.153127 0.252472 +v -0.357733 0.157451 0.280389 +v -0.351836 0.166836 0.274248 +v -0.351836 0.163776 0.254493 +v -0.337563 0.150965 0.238514 +v -0.337563 0.162246 0.244616 +v -0.317393 0.153127 0.252472 +v -0.323290 0.163776 0.254493 +v -0.317393 0.157451 0.280389 +v -0.323290 0.166836 0.274248 +v -0.337563 0.159613 0.294348 +v -0.337563 0.168366 0.284125 +v -0.337563 0.159613 0.294348 +v -0.337563 0.168366 0.284125 +v -0.323290 0.163776 0.254493 +v -0.337563 0.162246 0.244616 +v -0.337563 0.168366 0.284125 +v -0.323290 0.166836 0.274248 +v -0.351836 0.166836 0.274248 +v -0.351836 0.163776 0.254493 +v -0.337563 -0.127435 0.397225 +v -0.321615 -0.131133 0.386968 +v -0.328314 -0.116500 0.372933 +v -0.337563 -0.115496 0.379331 +v -0.321615 -0.138527 0.366454 +v -0.328314 -0.118510 0.360136 +v -0.337563 -0.142224 0.356198 +v -0.337563 -0.119514 0.353739 +v -0.337563 -0.142224 0.356198 +v -0.353511 -0.138527 0.366454 +v -0.346812 -0.118510 0.360137 +v -0.337563 -0.119514 0.353739 +v -0.353511 -0.131133 0.386968 +v -0.346812 -0.116500 0.372933 +v -0.337563 -0.080905 0.379581 +v -0.313826 -0.084424 0.370030 +v -0.322398 -0.059151 0.343349 +v -0.337563 -0.053610 0.351886 +v -0.313826 -0.100242 0.332992 +v -0.322398 -0.070233 0.326274 +v -0.337563 -0.103762 0.323441 +v -0.337563 -0.075774 0.317737 +v -0.337563 -0.103762 0.323441 +v -0.361301 -0.100242 0.332992 +v -0.352729 -0.070233 0.326274 +v -0.337563 -0.075774 0.317737 +v -0.361301 -0.084424 0.370030 +v -0.352729 -0.059151 0.343349 +v -0.337563 -0.080905 0.379581 +v -0.337563 -0.053610 0.351886 +v -0.337563 0.023452 0.310195 +v -0.325649 0.022175 0.301950 +v -0.327229 0.052690 0.295533 +v -0.337563 0.053798 0.302685 +v -0.325649 0.019621 0.285460 +v -0.327229 0.050474 0.281229 +v -0.337563 0.018344 0.277215 +v -0.337563 0.049367 0.274077 +v -0.337563 0.018344 0.277215 +v -0.349478 0.019621 0.285460 +v -0.347898 0.050474 0.281229 +v -0.337563 0.049367 0.274077 +v -0.349478 0.022175 0.301950 +v -0.347898 0.052690 0.295533 +v -0.325376 0.092754 0.271268 +v -0.317597 0.132978 0.257536 +v -0.317635 0.136945 0.285207 +v -0.325404 0.095138 0.288162 +v -0.337563 0.091561 0.262799 +v -0.337563 0.130993 0.243671 +v -0.337563 0.091561 0.262799 +v -0.349750 0.092754 0.271268 +v -0.357530 0.132978 0.257537 +v -0.337563 0.130993 0.243671 +v -0.349723 0.095138 0.288162 +v -0.357492 0.136945 0.285207 +v -0.337563 0.096328 0.296573 +v -0.337563 0.138924 0.298993 +v -0.328314 -0.061313 0.340018 +v -0.328314 -0.068071 0.329605 +v -0.326740 -0.038814 0.305780 +v -0.326740 -0.032710 0.319273 +v -0.337563 -0.071451 0.324398 +v -0.337563 -0.041865 0.299033 +v -0.337563 -0.071451 0.324398 +v -0.346812 -0.068071 0.329605 +v -0.348386 -0.038814 0.305780 +v -0.337563 -0.041865 0.299033 +v -0.346812 -0.061313 0.340018 +v -0.348386 -0.032710 0.319273 +v -0.337563 -0.057934 0.345225 +v -0.337563 -0.029659 0.326020 +v -0.328314 -0.061313 0.340018 +v -0.337563 -0.057934 0.345225 +v -0.322398 -0.115858 0.377025 +v -0.328314 -0.116500 0.372933 +v -0.328314 -0.118510 0.360136 +v -0.322398 -0.119152 0.356044 +v -0.328314 -0.068071 0.329605 +v -0.337563 -0.119514 0.353739 +v -0.337563 -0.120799 0.345554 +v -0.337563 -0.071451 0.324398 +v -0.337563 -0.120799 0.345554 +v -0.337563 -0.119514 0.353739 +v -0.346812 -0.118510 0.360137 +v -0.352729 -0.119152 0.356044 +v -0.346812 -0.068071 0.329605 +v -0.337563 -0.071451 0.324398 +v -0.346812 -0.116500 0.372933 +v -0.352729 -0.115858 0.377025 +v -0.346812 -0.061313 0.340018 +v -0.337563 -0.115496 0.379331 +v -0.337563 -0.114210 0.387516 +v -0.337563 -0.057934 0.345225 +v -0.337563 -0.114210 0.387516 +v -0.337563 -0.115496 0.379331 +v -0.320009 -0.150433 0.373239 +v -0.320009 -0.140534 0.395124 +v -0.337563 -0.155383 0.362297 +v -0.355117 -0.150433 0.373239 +v -0.337563 -0.155383 0.362297 +v -0.355117 -0.140534 0.395124 +v -0.337563 -0.135585 0.406067 +v -0.323999 0.138546 0.280338 +v -0.323999 0.135638 0.261564 +v -0.337563 0.134184 0.252177 +v -0.351127 0.135638 0.261564 +v -0.351127 0.138546 0.280338 +v -0.337563 0.140000 0.289724 +v -0.337563 0.140000 0.289724 +v -0.323006 0.055725 0.297919 +v -0.327229 0.052690 0.295533 +v -0.327229 0.050474 0.281229 +v -0.322966 0.052928 0.277678 +v -0.323999 0.135638 0.261564 +v -0.323999 0.138546 0.280338 +v -0.337563 0.049367 0.274077 +v -0.337563 0.051528 0.267524 +v -0.337563 0.134184 0.252177 +v -0.337563 0.051528 0.267524 +v -0.337563 0.049367 0.274077 +v -0.347898 0.050474 0.281229 +v -0.352161 0.052928 0.277678 +v -0.351127 0.135638 0.261564 +v -0.337563 0.134184 0.252177 +v -0.347898 0.052690 0.295533 +v -0.352120 0.055725 0.297919 +v -0.351127 0.138546 0.280338 +v -0.337563 0.053798 0.302685 +v -0.337563 0.057120 0.307988 +v -0.337563 0.140000 0.289724 +v -0.328351 -0.003103 0.306540 +v -0.328351 -0.006534 0.294361 +v -0.337563 -0.008249 0.288272 +v -0.337563 -0.008249 0.288272 +v -0.346776 -0.006534 0.294361 +v -0.346776 -0.003103 0.306540 +v -0.337563 -0.001388 0.312629 +v -0.337563 -0.155383 0.362297 +v -0.320009 -0.150433 0.373239 +v -0.320009 -0.140534 0.395124 +v -0.337563 -0.135585 0.406067 +v -0.355117 -0.140534 0.395124 +v -0.355117 -0.150433 0.373239 +v -0.281039 -0.082836 0.165109 +v -0.281039 -0.082836 0.199666 +v -0.386786 -0.082836 0.199666 +v -0.386786 -0.082836 0.165109 +v -0.386786 0.088875 0.199176 +v -0.386786 0.088875 0.164619 +v -0.281039 0.088875 0.164619 +v -0.281039 0.088875 0.199176 +v -0.274017 -0.124209 0.158852 +v -0.273006 -0.076783 0.158852 +v -0.273006 -0.076783 0.206902 +v -0.274017 -0.124209 0.215549 +v -0.325500 -0.082481 0.206902 +v -0.325500 -0.121004 0.215549 +v -0.394483 -0.125277 0.215549 +v -0.395157 -0.073222 0.206902 +v -0.395157 -0.073222 0.158852 +v -0.394483 -0.125277 0.158852 +v -0.273006 -0.076783 0.158852 +v -0.325500 -0.082481 0.158852 +v -0.325500 -0.121004 0.158852 +v -0.274017 -0.124209 0.158852 +v -0.394483 -0.125277 0.158852 +v -0.395157 -0.073222 0.158852 +v -0.272669 0.084176 0.158852 +v -0.274354 0.127328 0.158852 +v -0.274354 0.127016 0.213027 +v -0.272669 0.084176 0.206902 +v -0.333398 0.119893 0.213027 +v -0.331039 0.088806 0.206902 +v -0.393471 0.085244 0.206902 +v -0.397516 0.124879 0.213027 +v -0.397516 0.125192 0.158852 +v -0.393471 0.085244 0.158852 +v -0.274354 0.127328 0.158852 +v -0.333398 0.120206 0.158852 +v -0.331039 0.088806 0.158852 +v -0.272669 0.084176 0.158852 +v -0.393471 0.085244 0.158852 +v -0.397516 0.125192 0.158852 +v -0.321874 -0.051569 0.191459 +v -0.321874 -0.021273 0.189244 +v -0.326325 -0.030656 0.230408 +v -0.326325 -0.045416 0.231339 +v -0.352860 -0.021273 0.189244 +v -0.348409 -0.030656 0.230408 +v -0.352860 -0.051569 0.191459 +v -0.348409 -0.045416 0.231339 +v -0.326348 -0.025067 0.277540 +v -0.326348 -0.048713 0.277540 +v -0.348387 -0.048713 0.277540 +v -0.348387 -0.025067 0.277540 +v -0.322968 -0.054386 0.270191 +v -0.322968 -0.019309 0.274988 +v -0.322968 -0.013537 0.288986 +v -0.322968 -0.052750 0.301897 +v -0.348988 -0.013537 0.288986 +v -0.348988 -0.052750 0.301897 +v -0.348988 -0.054386 0.270191 +v -0.348988 -0.019309 0.274988 +v -0.348988 -0.019309 0.274988 +v -0.348988 -0.013537 0.288986 +v -0.348988 -0.054386 0.270191 +v -0.348988 -0.052750 0.301897 +v -0.322968 0.044584 0.244861 +v -0.322968 0.088392 0.247061 +v -0.322968 0.087109 0.262334 +v -0.322968 0.046407 0.268930 +v -0.348988 0.087109 0.262334 +v -0.348988 0.046407 0.268930 +v -0.348988 0.044584 0.244861 +v -0.348988 0.088392 0.247062 +v -0.348988 0.088392 0.247062 +v -0.348988 0.087109 0.262334 +v -0.348988 0.044584 0.244861 +v -0.348988 0.046407 0.268930 +v -0.321874 0.048453 0.190311 +v -0.321874 0.077050 0.194269 +v -0.326325 0.075773 0.223588 +v -0.326325 0.059011 0.224149 +v -0.352860 0.077050 0.194269 +v -0.348409 0.075773 0.223588 +v -0.326348 0.080353 0.249187 +v -0.326348 0.048498 0.251539 +v -0.348387 0.080353 0.249187 +v -0.348387 0.048498 0.251539 +v -0.348409 0.059011 0.224149 +v -0.352860 0.048453 0.190311 +v -0.408631 0.133840 0.350520 +v -0.408631 0.185495 0.343534 +v -0.417825 0.179489 0.389491 +v -0.417825 0.136514 0.390303 +v -0.409266 0.125617 0.440177 +v -0.409266 0.189861 0.447533 +v -0.460364 0.189861 0.437159 +v -0.460364 0.125617 0.429804 +v -0.460344 0.133840 0.357436 +v -0.460344 0.185495 0.350449 +v -0.408631 0.185495 0.343534 +v -0.460344 0.185495 0.350449 +v -0.460407 0.179489 0.391565 +v -0.417825 0.179489 0.389491 +v -0.460344 0.133840 0.357436 +v -0.408631 0.133840 0.350520 +v -0.417825 0.136514 0.390303 +v -0.460407 0.136514 0.392378 +v -0.409266 0.125617 0.440177 +v -0.460364 0.125617 0.429804 +v -0.460364 0.189861 0.437159 +v -0.409266 0.189861 0.447533 +v -0.418841 -0.187839 0.155999 +v -0.418841 -0.144497 0.155600 +v -0.418841 -0.150307 0.407394 +v -0.418841 -0.183765 0.405468 +v -0.448062 -0.144497 0.155600 +v -0.448062 -0.150307 0.407394 +v -0.448062 -0.187839 0.155999 +v -0.448062 -0.183765 0.405468 +v -0.418841 -0.144312 0.623881 +v -0.418841 -0.185604 0.619447 +v -0.448062 -0.185604 0.619447 +v -0.448062 -0.144312 0.623881 +v -0.420734 0.176813 0.158518 +v -0.420734 0.171165 0.398454 +v -0.420734 0.144015 0.400649 +v -0.420734 0.133152 0.158119 +v -0.449955 0.144015 0.400649 +v -0.449955 0.133152 0.158119 +v -0.449955 0.176813 0.158518 +v -0.449955 0.171165 0.398454 +v -0.420734 0.180689 0.619447 +v -0.420734 0.139397 0.623881 +v -0.449955 0.180689 0.619447 +v -0.449955 0.139397 0.623881 +v -0.456184 -0.164699 0.373593 +v -0.456184 0.158984 0.385251 +v -0.456184 0.159770 0.416030 +v -0.456184 -0.167725 0.421160 +v -0.575149 0.159770 0.416030 +v -0.575149 -0.167725 0.421160 +v -0.575149 -0.164699 0.373593 +v -0.575149 0.158984 0.385251 +v -0.408631 -0.146232 0.350403 +v -0.417825 -0.148906 0.390186 +v -0.417825 -0.191880 0.389373 +v -0.408631 -0.197887 0.343416 +v -0.409266 -0.138009 0.440060 +v -0.460364 -0.138009 0.429686 +v -0.460364 -0.202253 0.437042 +v -0.409266 -0.202253 0.447416 +v -0.460344 -0.146232 0.357319 +v -0.460344 -0.197887 0.350332 +v -0.408631 -0.197887 0.343416 +v -0.417825 -0.191880 0.389373 +v -0.460407 -0.191880 0.391448 +v -0.460344 -0.197887 0.350332 +v -0.460344 -0.146232 0.357319 +v -0.460407 -0.148906 0.392260 +v -0.417825 -0.148906 0.390186 +v -0.408631 -0.146232 0.350403 +v -0.460364 -0.138009 0.429686 +v -0.409266 -0.138009 0.440060 +v -0.409266 -0.202253 0.447416 +v -0.460364 -0.202253 0.437042 +v 0.588718 0.349642 0.006153 +v 0.631482 0.272193 0.008222 +v 0.631916 0.446990 0.008574 +v 0.581104 0.453087 0.026813 +v 0.631554 0.515283 0.001503 +v 0.574975 0.525073 0.024092 +v 0.725379 0.654587 -0.002505 +v 0.685266 0.588856 -0.000621 +v 0.461600 0.701101 -0.003347 +v 0.441820 0.655788 0.025780 +v 0.428120 0.569025 0.029422 +v 0.415274 0.488868 0.037569 +v 0.396565 0.369979 0.043260 +v 0.377371 0.241151 0.009510 +v 0.219986 0.378598 0.005976 +v 0.154045 0.317484 0.007776 +v 0.200713 0.501280 0.002643 +v 0.243517 0.494071 0.021185 +v 0.216612 0.564037 0.000855 +v 0.274236 0.560861 0.019101 +v 0.180606 0.708175 -0.003068 +v 0.187996 0.649980 -0.001472 +v 0.441820 0.655788 0.025780 +v 0.461600 0.701101 -0.003347 +v 0.415274 0.488868 0.037569 +v 0.428120 0.569025 0.029422 +v 0.396565 0.369979 0.043260 +v 0.377371 0.241151 0.009510 +v -0.114645 0.739535 -0.107646 +v -0.575491 0.739535 -0.107646 +v 0.319177 0.739535 -0.107646 +v 0.741905 0.739535 -0.107647 +v -0.575491 -0.721043 -0.107646 +v -0.114645 -0.721043 -0.107646 +v 0.319177 -0.721043 -0.107646 +v 0.741905 -0.721043 -0.107647 +v -0.575491 -0.721043 -0.107646 +v -0.575491 0.739535 -0.107646 +v 0.741905 -0.721043 -0.107647 +v 0.741905 0.739535 -0.107647 +v -0.447635 0.256695 0.664645 +v -0.417421 0.256695 0.664645 +v -0.417421 0.250623 0.658135 +v -0.447635 0.250623 0.658135 +v -0.417421 0.247516 0.726090 +v -0.417421 0.216328 0.692649 +v -0.417421 0.250623 0.658135 +v -0.447635 0.250623 0.658135 +v -0.447635 0.216328 0.692649 +v -0.447635 0.247516 0.726090 +v -0.447635 -0.254362 0.656174 +v -0.447635 -0.248290 0.649664 +v -0.417421 -0.248290 0.649664 +v -0.417421 -0.254362 0.656174 +v -0.417421 -0.248290 0.649664 +v -0.417421 -0.213995 0.684178 +v -0.417421 -0.245183 0.717619 +v -0.447635 -0.248290 0.649664 +v -0.447635 -0.213995 0.684178 +v -0.447635 -0.245183 0.717619 +v -0.411461 0.202230 0.614738 +v -0.411461 0.168495 0.670555 +v -0.411461 0.121505 0.652002 +v -0.411461 0.123459 0.612594 +v -0.411461 0.254991 0.726295 +v -0.411461 0.250444 0.747076 +v -0.411461 0.183746 0.775239 +v -0.411461 0.135465 0.729690 +v -0.411461 -0.200853 0.608645 +v -0.411461 -0.123031 0.616770 +v -0.411461 -0.121763 0.661191 +v -0.411461 -0.170962 0.670042 +v -0.411461 -0.247081 0.737319 +v -0.411461 -0.250693 0.715214 +v -0.411461 -0.140056 0.718761 +v -0.411461 -0.166506 0.772849 +v -0.411461 0.000381 0.754349 +v -0.411461 -0.000534 0.687263 +v -0.448569 0.168495 0.670555 +v -0.448569 0.121505 0.652002 +v -0.411461 0.121505 0.652002 +v -0.411461 0.168495 0.670555 +v -0.448569 0.123459 0.612594 +v -0.448569 0.202230 0.614738 +v -0.411461 0.202230 0.614738 +v -0.411461 0.123459 0.612594 +v -0.448569 0.170319 0.699099 +v -0.411461 0.170319 0.699099 +v -0.448569 0.212408 0.675560 +v -0.411461 0.212408 0.675560 +v -0.448569 0.254991 0.726295 +v -0.411461 0.254991 0.726295 +v -0.448569 0.254991 0.726295 +v -0.448569 0.250444 0.747076 +v -0.411461 0.250444 0.747076 +v -0.411461 0.254991 0.726295 +v -0.448569 0.209834 0.750638 +v -0.411461 0.209834 0.750638 +v -0.448569 0.183746 0.775239 +v -0.411461 0.183746 0.775239 +v -0.448569 0.135465 0.729690 +v -0.411461 0.135465 0.729690 +v -0.448569 0.000381 0.754349 +v -0.411461 0.000381 0.754349 +v -0.448569 -0.000534 0.687263 +v -0.411461 -0.000534 0.687263 +v -0.448569 -0.200853 0.608645 +v -0.448569 -0.123031 0.616770 +v -0.411461 -0.123031 0.616770 +v -0.411461 -0.200853 0.608645 +v -0.448569 -0.121763 0.661191 +v -0.448569 -0.170962 0.670042 +v -0.411461 -0.170962 0.670042 +v -0.411461 -0.121763 0.661191 +v -0.448569 -0.213822 0.675616 +v -0.411461 -0.213822 0.675616 +v -0.448569 -0.173571 0.699646 +v -0.411461 -0.173571 0.699646 +v -0.448569 -0.212562 0.745240 +v -0.448569 -0.247081 0.737319 +v -0.411461 -0.247081 0.737319 +v -0.411461 -0.212562 0.745240 +v -0.448569 -0.250693 0.715214 +v -0.411461 -0.250693 0.715214 +v -0.448569 -0.250693 0.715214 +v -0.411461 -0.250693 0.715214 +v -0.448569 -0.140056 0.718761 +v -0.411461 -0.140056 0.718761 +v -0.448569 -0.166506 0.772849 +v -0.411461 -0.166506 0.772849 +v -0.426600 -0.021946 0.394045 +v -0.425094 -0.005993 0.384050 +v -0.434734 -0.004954 0.437012 +v -0.434423 -0.026875 0.437641 +v -0.427058 0.010334 0.393120 +v -0.435045 0.016967 0.436385 +v -0.440123 -0.022144 0.391583 +v -0.438617 -0.006191 0.381588 +v -0.440582 0.010136 0.390658 +v -0.440582 0.010136 0.390658 +v -0.448568 0.016769 0.433922 +v -0.440123 -0.022144 0.391583 +v -0.447946 -0.027073 0.435178 +v -0.499039 0.002764 0.569104 +v -0.495826 0.000129 0.569141 +v -0.481623 0.010780 0.556359 +v -0.490260 0.019267 0.556221 +v -0.494319 -0.003466 0.569225 +v -0.477960 -0.001157 0.556651 +v -0.494862 -0.007198 0.569335 +v -0.480110 -0.013811 0.557030 +v -0.497331 -0.010211 0.569448 +v -0.487580 -0.024282 0.557410 +v -0.501159 -0.011816 0.569537 +v -0.498659 -0.030171 0.557702 +v -0.505471 -0.011644 0.569582 +v -0.510809 -0.030130 0.557841 +v -0.509279 -0.009735 0.569573 +v -0.521246 -0.024167 0.557795 +v -0.511709 -0.006527 0.569511 +v -0.527581 -0.013650 0.557573 +v -0.512206 -0.002754 0.569411 +v -0.528360 -0.000986 0.557228 +v -0.512206 -0.002754 0.569411 +v -0.510655 0.000719 0.569296 +v -0.523407 0.010922 0.556837 +v -0.528360 -0.000986 0.557228 +v -0.507412 0.003097 0.569192 +v -0.513855 0.019347 0.556491 +v -0.503220 0.003835 0.569123 +v -0.501892 0.022358 0.556269 +v -0.472487 0.016906 0.541947 +v -0.484811 0.029016 0.541750 +v -0.467260 -0.000127 0.542364 +v -0.470327 -0.018183 0.542905 +v -0.480986 -0.033124 0.543447 +v -0.496795 -0.041527 0.543864 +v -0.514132 -0.041468 0.544063 +v -0.529026 -0.032961 0.543996 +v -0.538064 -0.017953 0.543680 +v -0.539176 0.000117 0.543187 +v -0.532108 0.017109 0.542629 +v -0.539176 0.000117 0.543187 +v -0.518478 0.029130 0.542135 +v -0.501409 0.033428 0.541818 +v -0.465189 0.021695 0.523495 +v -0.480484 0.036724 0.523251 +v -0.458701 0.000555 0.524013 +v -0.462508 -0.021853 0.524684 +v -0.475737 -0.040396 0.525356 +v -0.495357 -0.050825 0.525875 +v -0.516873 -0.050752 0.526121 +v -0.535358 -0.040193 0.526038 +v -0.546575 -0.021567 0.525646 +v -0.547956 0.000858 0.525034 +v -0.539183 0.021947 0.524342 +v -0.547956 0.000858 0.525034 +v -0.522267 0.036866 0.523729 +v -0.501083 0.042199 0.523335 +v -0.460149 0.024880 0.502591 +v -0.477526 0.041955 0.502313 +v -0.452778 0.000862 0.503178 +v -0.457103 -0.024597 0.503941 +v -0.472132 -0.045664 0.504705 +v -0.494423 -0.057513 0.505294 +v -0.518869 -0.057430 0.505573 +v -0.539870 -0.045434 0.505480 +v -0.552614 -0.024272 0.505034 +v -0.554182 0.001206 0.504338 +v -0.544216 0.025165 0.503552 +v -0.554182 0.001206 0.504338 +v -0.524997 0.042116 0.502856 +v -0.500930 0.048175 0.502409 +v -0.457622 0.026361 0.484489 +v -0.476072 0.044490 0.484194 +v -0.449797 0.000861 0.485113 +v -0.454388 -0.026168 0.485923 +v -0.470345 -0.048535 0.486733 +v -0.494011 -0.061115 0.487358 +v -0.519966 -0.061027 0.487655 +v -0.542262 -0.048291 0.487556 +v -0.555793 -0.025824 0.487083 +v -0.557458 0.001227 0.486344 +v -0.546876 0.026664 0.485510 +v -0.557458 0.001227 0.486344 +v -0.526472 0.044661 0.484770 +v -0.500919 0.051094 0.484296 +v -0.502162 0.005192 0.577600 +v -0.503183 -0.013678 0.578140 +v -0.502403 0.004271 0.567584 +v -0.503348 -0.013178 0.568084 +v -0.526710 0.074191 0.427093 +v -0.564217 0.051490 0.427803 +v -0.551633 0.040741 0.484104 +v -0.521847 0.058768 0.483541 +v -0.585345 0.013085 0.429004 +v -0.568412 0.010241 0.485058 +v -0.584433 -0.030736 0.430374 +v -0.567687 -0.024558 0.486146 +v -0.561724 -0.068230 0.431547 +v -0.549653 -0.054333 0.487078 +v -0.561724 -0.068230 0.431547 +v -0.523304 -0.089350 0.432207 +v -0.519142 -0.071106 0.487602 +v -0.549653 -0.054333 0.487078 +v -0.479467 -0.088438 0.432179 +v -0.484330 -0.070382 0.487579 +v -0.441960 -0.065737 0.431469 +v -0.454544 -0.052354 0.487016 +v -0.420832 -0.027332 0.430268 +v -0.437765 -0.021855 0.486062 +v -0.421744 0.016489 0.428898 +v -0.438490 0.012945 0.484974 +v -0.444453 0.053983 0.427725 +v -0.456523 0.042720 0.484043 +v -0.482873 0.075103 0.427065 +v -0.487034 0.059493 0.483518 +v -0.437765 -0.021855 0.486062 +v -0.438490 0.012945 0.484974 +v -0.551633 0.040741 0.484104 +v -0.568412 0.010241 0.485058 +v -0.567687 -0.024558 0.486146 +v -0.454544 -0.052354 0.487016 +v -0.390863 0.372365 0.563614 +v -0.379384 0.370165 0.564704 +v -0.374701 0.375703 0.553807 +v -0.384889 0.379430 0.549714 +v -0.394361 0.358094 0.569924 +v -0.382127 0.358977 0.569651 +v -0.393748 0.343295 0.565690 +v -0.381645 0.347376 0.566332 +v -0.389309 0.334893 0.552894 +v -0.378165 0.340789 0.556301 +v -0.383121 0.336819 0.537524 +v -0.373315 0.342299 0.544251 +v -0.378081 0.348171 0.526770 +v -0.369364 0.351199 0.535821 +v -0.376547 0.363639 0.525666 +v -0.368161 0.363324 0.534955 +v -0.379235 0.375984 0.534727 +v -0.370268 0.373002 0.542059 +v -0.372968 0.362982 0.560709 +v -0.370894 0.365436 0.555881 +v -0.374183 0.358026 0.562900 +v -0.368529 0.354580 0.547913 +v -0.367996 0.359952 0.547529 +v -0.368930 0.364239 0.550676 +v -0.386589 0.463970 0.536790 +v -0.408036 0.453039 0.583917 +v -0.383103 0.454857 0.597471 +v -0.361655 0.465788 0.550345 +v -0.426097 0.425737 0.625667 +v -0.401163 0.427555 0.639222 +v -0.437540 0.370240 0.660192 +v -0.411237 0.365796 0.663236 +v -0.440289 0.337570 0.644265 +v -0.412666 0.335410 0.652529 +v -0.429935 0.294774 0.652229 +v -0.411003 0.302276 0.649562 +v -0.418209 0.251873 0.586556 +v -0.388293 0.257418 0.602502 +v -0.394157 0.247884 0.526420 +v -0.369223 0.249701 0.539975 +v -0.371245 0.269351 0.482693 +v -0.350460 0.275970 0.503023 +v -0.365383 0.292132 0.459989 +v -0.340449 0.293949 0.473544 +v -0.362129 0.338458 0.431748 +v -0.337195 0.340276 0.445303 +v -0.354648 0.411016 0.446709 +v -0.329714 0.412834 0.460264 +v -0.367637 0.447926 0.483090 +v -0.342704 0.449744 0.496645 +v -0.382137 0.439059 0.593298 +v -0.363011 0.449805 0.551913 +v -0.396219 0.417771 0.625853 +v -0.409818 0.365747 0.641387 +v -0.402661 0.343732 0.634239 +v -0.404339 0.309706 0.633860 +v -0.394160 0.276745 0.592785 +v -0.370636 0.267443 0.545475 +v -0.361731 0.286772 0.514832 +v -0.344513 0.304950 0.484864 +v -0.345247 0.344154 0.465719 +v -0.337555 0.401132 0.478405 +v -0.347283 0.436886 0.506565 +v -0.315313 0.581021 0.366718 +v -0.321318 0.581021 0.497917 +v -0.321326 0.535899 0.497930 +v -0.315313 0.531147 0.366718 +v -0.334654 0.573301 0.608309 +v -0.334654 0.533609 0.608309 +v -0.290416 0.544425 0.729348 +v -0.290416 0.506918 0.699577 +v -0.244261 0.482028 0.825101 +v -0.242863 0.446929 0.784232 +v -0.244261 0.482028 0.825101 +v -0.176609 0.373210 0.900818 +v -0.176609 0.359250 0.848096 +v -0.242863 0.446929 0.784232 +v -0.458150 0.581021 0.366718 +v -0.315313 0.581021 0.366718 +v -0.315313 0.531147 0.366718 +v -0.458150 0.531147 0.366718 +v -0.321326 0.535899 0.497930 +v -0.458123 0.535899 0.497930 +v -0.458123 0.581021 0.497917 +v -0.458150 0.581021 0.366718 +v -0.334654 0.533609 0.608309 +v -0.458111 0.533609 0.608309 +v -0.458111 0.573301 0.608309 +v -0.290416 0.506918 0.699577 +v -0.458111 0.506918 0.699577 +v -0.458111 0.544425 0.729348 +v -0.242863 0.446929 0.784232 +v -0.458126 0.446929 0.784232 +v -0.458122 0.482028 0.825101 +v -0.458126 0.446929 0.784232 +v -0.458205 0.359250 0.848096 +v -0.458205 0.373210 0.900818 +v -0.458122 0.482028 0.825101 +v -0.458205 0.373210 0.900818 +v -0.458205 0.000000 0.924882 +v -0.176609 0.000000 0.920199 +v -0.176609 0.373210 0.900818 +v -0.176609 0.000000 0.870731 +v -0.176609 0.359250 0.848096 +v -0.176609 0.359250 0.848096 +v -0.176609 0.000000 0.870731 +v -0.458205 0.000000 0.856508 +v -0.458205 0.359250 0.848096 +v -0.315313 -0.581021 0.366718 +v -0.315313 -0.531147 0.366718 +v -0.321326 -0.535899 0.497930 +v -0.321318 -0.581021 0.497917 +v -0.334654 -0.533609 0.608309 +v -0.334654 -0.573301 0.608309 +v -0.290416 -0.506918 0.699577 +v -0.290416 -0.544425 0.729348 +v -0.238969 -0.435946 0.791554 +v -0.244178 -0.471045 0.832424 +v -0.244178 -0.471045 0.832424 +v -0.238969 -0.435946 0.791554 +v -0.176609 -0.359250 0.848096 +v -0.176609 -0.373210 0.900818 +v -0.458150 -0.581021 0.366718 +v -0.458150 -0.531147 0.366718 +v -0.315313 -0.531147 0.366718 +v -0.315313 -0.581021 0.366718 +v -0.458123 -0.535899 0.497930 +v -0.321326 -0.535899 0.497930 +v -0.458123 -0.581021 0.497917 +v -0.458150 -0.581021 0.366718 +v -0.458111 -0.533609 0.608309 +v -0.334654 -0.533609 0.608309 +v -0.458111 -0.573301 0.608309 +v -0.458111 -0.506918 0.699577 +v -0.290416 -0.506918 0.699577 +v -0.458111 -0.544425 0.729348 +v -0.458126 -0.435946 0.791554 +v -0.238969 -0.435946 0.791554 +v -0.458122 -0.471045 0.832424 +v -0.458126 -0.435946 0.791554 +v -0.458205 -0.359250 0.848096 +v -0.458205 -0.373210 0.900818 +v -0.458122 -0.471045 0.832424 +v -0.458205 -0.373210 0.900818 +v -0.176609 -0.373210 0.900818 +v -0.176609 -0.359250 0.848096 +v -0.176609 0.000000 0.870731 +v -0.176609 -0.359250 0.848096 +v -0.458205 -0.359250 0.848096 +v 0.499370 -0.358474 -0.003355 +v 0.468167 -0.429543 -0.003355 +v 0.434727 -0.433233 0.102648 +v 0.479455 -0.331359 0.102648 +v 0.468167 -0.429543 -0.003355 +v 0.514114 -0.492100 -0.003355 +v 0.500589 -0.522905 0.102648 +v 0.434727 -0.433233 0.102648 +v 0.514114 -0.492100 -0.003355 +v 0.591263 -0.483588 -0.003355 +v 0.611179 -0.510702 0.102648 +v 0.500589 -0.522905 0.102648 +v 0.591263 -0.483588 -0.003355 +v 0.622466 -0.412518 -0.003355 +v 0.655906 -0.408828 0.102648 +v 0.611179 -0.510702 0.102648 +v 0.622466 -0.412518 -0.003355 +v 0.576519 -0.349961 -0.003355 +v 0.590044 -0.319156 0.102648 +v 0.655906 -0.408828 0.102648 +v 0.576519 -0.349961 -0.003355 +v 0.499370 -0.358474 -0.003355 +v 0.479455 -0.331359 0.102648 +v 0.590044 -0.319156 0.102648 +v 0.514114 -0.492100 0.196085 +v 0.591263 -0.483588 0.196085 +v 0.622466 -0.412518 0.196085 +v 0.576519 -0.349961 0.196085 +v 0.499370 -0.358474 0.196085 +v 0.468167 -0.429543 0.196085 +v 0.622466 -0.412518 0.196085 +v 0.591263 -0.483588 0.196085 +v 0.591263 -0.483588 0.196085 +v 0.514114 -0.492100 0.196085 +v 0.514114 -0.492100 0.196085 +v 0.468167 -0.429543 0.196085 +v 0.468167 -0.429543 0.196085 +v 0.499370 -0.358474 0.196085 +v 0.499370 -0.358474 0.196085 +v 0.576519 -0.349961 0.196085 +v 0.576519 -0.349961 0.196085 +v 0.622466 -0.412518 0.196085 +v -0.430337 0.304702 -0.003355 +v -0.361318 0.269194 -0.003355 +v -0.360410 0.250475 0.102648 +v -0.446095 0.294556 0.102648 +v -0.361318 0.269194 -0.003355 +v -0.296058 0.311213 -0.003355 +v -0.279393 0.302640 0.102648 +v -0.296058 0.311213 -0.003355 +v -0.299817 0.388739 -0.003355 +v -0.284060 0.398885 0.102648 +v -0.279393 0.302640 0.102648 +v -0.299817 0.388739 -0.003355 +v -0.368837 0.424247 -0.003355 +v -0.369744 0.442966 0.102648 +v -0.284060 0.398885 0.102648 +v -0.368837 0.424247 -0.003355 +v -0.434097 0.382228 -0.003355 +v -0.450762 0.390801 0.102648 +v -0.369744 0.442966 0.102648 +v -0.434097 0.382228 -0.003355 +v -0.430337 0.304702 -0.003355 +v -0.450762 0.390801 0.102648 +v -0.296058 0.311213 0.196085 +v -0.299817 0.388739 0.196085 +v -0.368836 0.424247 0.196085 +v -0.434097 0.382228 0.196085 +v -0.430337 0.304702 0.196085 +v -0.361318 0.269194 0.196085 +v -0.368836 0.424247 0.196085 +v -0.299817 0.388739 0.196085 +v -0.299817 0.388739 0.196085 +v -0.296058 0.311213 0.196085 +v -0.296058 0.311213 0.196085 +v -0.361318 0.269194 0.196085 +v -0.361318 0.269194 0.196085 +v -0.430337 0.304702 0.196085 +v -0.430337 0.304702 0.196085 +v -0.434097 0.382228 0.196085 +v -0.434097 0.382228 0.196085 +v -0.368836 0.424247 0.196085 +v -0.350648 -0.280157 0.474883 +v -0.334665 -0.289444 0.476013 +v -0.329726 -0.282446 0.478837 +v -0.349579 -0.273411 0.477077 +v -0.370365 -0.276471 0.483171 +v -0.368242 -0.284710 0.479712 +v -0.320842 -0.344889 0.488932 +v -0.327015 -0.352981 0.492732 +v -0.322270 -0.360971 0.501280 +v -0.313394 -0.345731 0.493142 +v -0.317049 -0.333836 0.484677 +v -0.310247 -0.335173 0.488864 +v -0.325201 -0.304513 0.480229 +v -0.318311 -0.320094 0.483319 +v -0.307735 -0.318939 0.488458 +v -0.315653 -0.299264 0.484534 +v -0.336953 -0.292064 0.480608 +v -0.328405 -0.281374 0.485313 +v -0.326285 -0.277722 0.470906 +v -0.349658 -0.268478 0.471374 +v -0.373965 -0.270408 0.476684 +v -0.319472 -0.371829 0.496722 +v -0.307422 -0.348749 0.487407 +v -0.304843 -0.338568 0.483644 +v -0.300092 -0.317875 0.479089 +v -0.308154 -0.297435 0.474725 +v -0.322363 -0.274522 0.472659 +v -0.327140 -0.278937 0.462623 +v -0.350569 -0.269455 0.465282 +v -0.375147 -0.270390 0.471478 +v -0.320641 -0.374709 0.487046 +v -0.308011 -0.350530 0.479888 +v -0.305419 -0.340295 0.476550 +v -0.300993 -0.319833 0.468534 +v -0.309153 -0.299285 0.464178 +v -0.323707 -0.276657 0.459579 +v -0.373319 -0.280824 0.463280 +v -0.351947 -0.276974 0.461263 +v -0.332869 -0.287078 0.457267 +v -0.326164 -0.366707 0.475067 +v -0.316348 -0.350083 0.473251 +v -0.313042 -0.339289 0.470052 +v -0.312300 -0.323448 0.460510 +v -0.321016 -0.303958 0.456112 +v -0.333510 -0.288895 0.450943 +v -0.336269 -0.291807 0.465214 +v -0.351822 -0.281886 0.466983 +v -0.369719 -0.286886 0.469766 +v -0.328962 -0.355849 0.479625 +v -0.322320 -0.347066 0.478987 +v -0.318446 -0.335895 0.475271 +v -0.320385 -0.323012 0.469524 +v -0.327300 -0.307523 0.466312 +v -0.339477 -0.295782 0.463620 +v -0.350648 -0.280157 0.474883 +v -0.334665 -0.289444 0.476013 +v -0.368242 -0.284710 0.479712 +v -0.320842 -0.344889 0.488932 +v -0.327015 -0.352981 0.492732 +v -0.317049 -0.333836 0.484677 +v -0.325201 -0.304513 0.480229 +v -0.318311 -0.320094 0.483319 +v -0.336953 -0.292064 0.480608 +v -0.320643 -0.332003 0.487932 +v -0.309387 -0.333431 0.494623 +v -0.322877 -0.335294 0.472893 +v -0.320643 -0.332003 0.487932 +v -0.313931 -0.340125 0.464035 +v -0.301320 -0.340945 0.474375 +v -0.300395 -0.338232 0.485780 +v -0.334665 -0.289444 0.476013 +v -0.336269 -0.291807 0.465214 +v -0.329726 -0.282446 0.478837 +v -0.334665 -0.289444 0.476013 +v -0.326285 -0.277722 0.470906 +v -0.327140 -0.278937 0.462623 +v -0.332869 -0.287078 0.457267 +v -0.317049 -0.333836 0.484677 +v -0.310247 -0.335173 0.488864 +v -0.318446 -0.335895 0.475271 +v -0.317049 -0.333836 0.484677 +v -0.313042 -0.339289 0.470052 +v -0.305419 -0.340295 0.476550 +v -0.304843 -0.338568 0.483644 +v -0.326947 -0.383792 0.461178 +v -0.349945 -0.395793 0.443629 +v -0.360427 -0.371523 0.441323 +v -0.338447 -0.360053 0.458095 +v -0.349945 -0.395793 0.443629 +v -0.377886 -0.407066 0.443056 +v -0.387130 -0.382297 0.440775 +v -0.360427 -0.371523 0.441323 +v -0.403282 -0.414590 0.459613 +v -0.411402 -0.389488 0.456599 +v -0.419329 -0.416350 0.488864 +v -0.426738 -0.391170 0.484555 +v -0.421726 -0.411873 0.522972 +v -0.429029 -0.386891 0.517152 +v -0.409832 -0.402360 0.552796 +v -0.417661 -0.377799 0.545656 +v -0.386833 -0.390359 0.570345 +v -0.395681 -0.366329 0.562428 +v -0.358892 -0.379085 0.570918 +v -0.368978 -0.355555 0.562976 +v -0.333496 -0.371561 0.554361 +v -0.344706 -0.348364 0.547152 +v -0.317450 -0.369801 0.525110 +v -0.329370 -0.346682 0.519196 +v -0.315052 -0.374278 0.491003 +v -0.327079 -0.350961 0.486599 +v -0.397269 -0.291636 0.537434 +v -0.377640 -0.285825 0.524608 +v -0.388754 -0.262472 0.517548 +v -0.407352 -0.267978 0.529701 +v -0.418873 -0.300357 0.536965 +v -0.427821 -0.276241 0.529257 +v -0.436663 -0.309650 0.523326 +v -0.444677 -0.285046 0.516333 +v -0.445872 -0.317026 0.500172 +v -0.453403 -0.292035 0.494395 +v -0.444034 -0.320509 0.473706 +v -0.451661 -0.295335 0.469319 +v -0.431639 -0.319164 0.451021 +v -0.439918 -0.294061 0.447825 +v -0.412011 -0.313353 0.438195 +v -0.421320 -0.288555 0.435672 +v -0.390407 -0.304632 0.438664 +v -0.400850 -0.280292 0.436117 +v -0.372617 -0.295339 0.452303 +v -0.390407 -0.304632 0.438664 +v -0.400850 -0.280292 0.436117 +v -0.383994 -0.271487 0.449040 +v -0.363407 -0.287963 0.475458 +v -0.375268 -0.264498 0.470978 +v -0.365246 -0.284480 0.501923 +v -0.377010 -0.261198 0.496054 +v -0.370091 -0.358239 0.553852 +v -0.349585 -0.352168 0.540452 +v -0.381481 -0.288575 0.519272 +v -0.398229 -0.293533 0.530216 +v -0.392660 -0.367349 0.553362 +v -0.416662 -0.300974 0.529816 +v -0.411245 -0.377058 0.539113 +v -0.431841 -0.308903 0.518178 +v -0.420866 -0.384764 0.514924 +v -0.439699 -0.315197 0.498422 +v -0.418946 -0.388402 0.487276 +v -0.438130 -0.318168 0.475841 +v -0.405998 -0.386997 0.463577 +v -0.427555 -0.317021 0.456485 +v -0.385492 -0.380926 0.450177 +v -0.410807 -0.312063 0.445542 +v -0.362922 -0.371816 0.450667 +v -0.392374 -0.304622 0.445942 +v -0.344337 -0.362107 0.464916 +v -0.362922 -0.371816 0.450667 +v -0.392374 -0.304622 0.445942 +v -0.377195 -0.296693 0.457579 +v -0.334716 -0.354401 0.489105 +v -0.369337 -0.290399 0.477336 +v -0.336637 -0.350763 0.516753 +v -0.370906 -0.287428 0.499917 +v -0.362922 -0.371816 0.450667 +v -0.344337 -0.362107 0.464916 +v -0.385492 -0.380926 0.450177 +v -0.362922 -0.371816 0.450667 +v -0.405998 -0.386997 0.463577 +v -0.418946 -0.388402 0.487276 +v -0.420866 -0.384764 0.514924 +v -0.411245 -0.377058 0.539113 +v -0.392660 -0.367349 0.553362 +v -0.370091 -0.358239 0.553852 +v -0.349585 -0.352168 0.540452 +v -0.336637 -0.350763 0.516753 +v -0.334716 -0.354401 0.489105 +v -0.398229 -0.293533 0.530216 +v -0.381481 -0.288575 0.519272 +v -0.416662 -0.300974 0.529816 +v -0.431841 -0.308903 0.518178 +v -0.439699 -0.315197 0.498422 +v -0.438130 -0.318168 0.475841 +v -0.427555 -0.317021 0.456485 +v -0.410807 -0.312063 0.445542 +v -0.392374 -0.304622 0.445942 +v -0.377195 -0.296693 0.457579 +v -0.392374 -0.304622 0.445942 +v -0.369337 -0.290399 0.477336 +v -0.370906 -0.287428 0.499917 +v -0.404246 -0.280731 0.449455 +v -0.392141 -0.274407 0.458736 +v -0.418947 -0.286666 0.449135 +v -0.404246 -0.280731 0.449455 +v -0.432304 -0.290620 0.457863 +v -0.440738 -0.291535 0.473300 +v -0.441989 -0.289165 0.491309 +v -0.435722 -0.284146 0.507065 +v -0.423617 -0.277822 0.516346 +v -0.408916 -0.271888 0.516665 +v -0.395559 -0.267933 0.507937 +v -0.387125 -0.267019 0.492501 +v -0.385874 -0.269388 0.474492 +v -0.377886 -0.407066 0.443056 +v -0.349945 -0.395793 0.443629 +v -0.358892 -0.379085 0.570918 +v -0.386833 -0.390359 0.570345 +v -0.326947 -0.383792 0.461178 +v -0.333496 -0.371561 0.554361 +v -0.409832 -0.402360 0.552796 +v -0.403282 -0.414590 0.459613 +v -0.421726 -0.411873 0.522972 +v -0.419329 -0.416350 0.488864 +v -0.315052 -0.374278 0.491003 +v -0.317450 -0.369801 0.525110 +v 0.130418 -0.002096 0.158016 +v 0.125108 -0.017533 0.155232 +v 0.123871 -0.047969 0.273397 +v 0.132647 -0.022455 0.277999 +v 0.135999 -0.029571 0.151956 +v 0.141871 -0.067865 0.267983 +v 0.152199 -0.026172 0.151464 +v 0.168646 -0.062247 0.267170 +v 0.157509 -0.010735 0.154248 +v 0.177422 -0.036733 0.271772 +v 0.146618 0.001303 0.157524 +v 0.159422 -0.016837 0.277186 +v 0.130418 -0.002096 0.158016 +v 0.132647 -0.022455 0.277999 +v 0.132647 -0.022455 0.277999 +v 0.123871 -0.047969 0.273397 +v 0.141871 -0.067865 0.267983 +v 0.168646 -0.062247 0.267170 +v 0.177422 -0.036733 0.271772 +v 0.159422 -0.016837 0.277186 +v -0.151840 0.442825 0.150534 +v -0.125064 0.570251 0.150534 +v -0.125064 0.570251 0.176347 +v -0.151840 0.442825 0.176347 +v -0.212470 0.586103 0.193445 +v -0.239245 0.458678 0.193445 +v -0.212470 0.586103 0.150534 +v -0.239245 0.458678 0.150534 +v -0.125064 0.570251 0.150534 +v -0.212470 0.586103 0.150534 +v -0.239245 0.458678 0.150534 +v -0.151840 0.442825 0.150534 +v -0.249346 0.412439 0.054440 +v 0.115625 0.346246 0.054439 +v 0.115625 0.346246 0.131554 +v -0.249346 0.412439 0.131554 +v -0.204283 0.626893 0.131554 +v 0.160688 0.560700 0.131554 +v 0.160688 0.560700 0.054439 +v -0.204283 0.626893 0.054440 +v 0.160688 0.560700 0.054439 +v 0.160688 0.560700 0.131554 +v -0.030698 0.594196 0.074088 +v 0.118020 0.567224 0.074088 +v 0.134023 0.643383 0.076372 +v -0.014695 0.670356 0.076372 +v 0.134023 0.643383 0.053193 +v -0.014695 0.670356 0.053193 +v -0.014695 0.670356 0.053193 +v 0.134023 0.643383 0.053193 +v 0.118020 0.567224 0.055383 +v -0.030698 0.594196 0.055383 +v 0.118020 0.567224 0.055383 +v 0.134023 0.643383 0.053193 +v -0.014695 0.670356 0.053193 +v -0.030698 0.594196 0.055383 +v -0.001930 0.631416 -0.003114 +v 0.106431 0.611763 -0.003114 +v 0.116103 0.610008 0.058063 +v -0.011602 0.633170 0.058063 +v -0.006190 0.658929 0.058063 +v 0.121516 0.635767 0.058063 +v 0.111844 0.637521 -0.003114 +v 0.003482 0.657175 -0.003114 +v 0.106431 0.611763 -0.003114 +v 0.116103 0.610008 0.058063 +v -0.001930 0.631416 -0.003114 +v -0.011602 0.633170 0.058063 +v -0.269322 0.384617 -0.003288 +v 0.130676 0.312070 -0.003288 +v 0.133519 0.325597 0.055359 +v -0.266479 0.398144 0.055359 +v 0.186922 0.579744 0.055359 +v -0.213076 0.652290 0.055359 +v 0.189765 0.593271 -0.003288 +v -0.210233 0.665817 -0.003288 +v 0.130676 0.312070 -0.003288 +v 0.189765 0.593271 -0.003288 +v -0.210233 0.665817 -0.003288 +v -0.269322 0.384617 -0.003288 +v -0.260499 0.407943 0.119982 +v 0.129778 0.337160 0.114963 +v 0.129778 0.337160 0.151157 +v -0.260499 0.407943 0.151157 +v 0.177965 0.566483 0.151157 +v -0.212311 0.637266 0.151157 +v 0.177965 0.566483 0.114963 +v -0.212311 0.637266 0.119982 +v -0.212311 0.637266 0.119982 +v 0.177965 0.566483 0.114963 +v 0.129778 0.337160 0.114963 +v -0.260499 0.407943 0.119982 +v 0.129778 0.337160 0.114963 +v 0.177965 0.566483 0.114963 +v -0.212311 0.637266 0.119982 +v -0.260499 0.407943 0.119982 +v 0.222300 -0.596550 0.118426 +v 0.245786 -0.579001 0.118426 +v 0.212467 -0.309309 0.120719 +v 0.183450 -0.306128 0.120719 +v 0.245786 -0.579001 0.118426 +v 0.243482 -0.544751 0.124890 +v 0.184260 -0.556156 0.124890 +v 0.188087 -0.593086 0.118426 +v 0.156860 -0.315354 0.120719 +v 0.161260 -0.334844 0.124890 +v 0.216432 -0.328119 0.124890 +v 0.212467 -0.309309 0.120719 +v 0.188087 -0.593086 0.118426 +v 0.156860 -0.315354 0.120719 +v 0.235011 -0.439582 0.134033 +v 0.173953 -0.451037 0.134033 +v 0.173953 -0.451037 0.114024 +v 0.235011 -0.439582 0.114024 +v 0.119065 -0.584458 0.108325 +v 0.151411 -0.590904 0.108325 +v 0.117136 -0.306193 0.112123 +v 0.087578 -0.319203 0.112123 +v 0.186388 -0.578329 0.108325 +v 0.184027 -0.554069 0.119031 +v 0.116544 -0.560137 0.119031 +v 0.119065 -0.584458 0.108325 +v 0.154635 -0.305797 0.112123 +v 0.186388 -0.578329 0.108325 +v 0.173755 -0.457917 0.138312 +v 0.105557 -0.464577 0.138312 +v 0.105557 -0.464577 0.108165 +v 0.173755 -0.457917 0.108165 +v 0.087578 -0.319203 0.112123 +v 0.093980 -0.351118 0.119031 +v 0.161651 -0.336739 0.119031 +v 0.154635 -0.305797 0.112123 +v 0.086177 -0.601870 0.107347 +v 0.118307 -0.589580 0.107347 +v 0.089083 -0.325584 0.111290 +v 0.058206 -0.330401 0.111290 +v 0.118307 -0.589580 0.107347 +v 0.118340 -0.572183 0.118464 +v 0.055611 -0.565524 0.118464 +v 0.063742 -0.588814 0.107347 +v 0.034755 -0.344658 0.111290 +v 0.037224 -0.360889 0.118464 +v 0.093361 -0.344161 0.118464 +v 0.089083 -0.325584 0.111290 +v 0.063742 -0.588814 0.107347 +v 0.034755 -0.344658 0.111290 +v 0.047579 -0.457113 0.107598 +v 0.047579 -0.457113 0.133906 +v 0.105951 -0.463304 0.107598 +v 0.105951 -0.463304 0.133906 +v 0.123351 -0.395821 0.168510 +v 0.134459 -0.392509 0.163833 +v 0.120835 -0.407669 0.170849 +v 0.129427 -0.416206 0.168510 +v 0.140535 -0.412895 0.163833 +v 0.143051 -0.401047 0.161494 +v 0.123998 -0.393911 0.175660 +v 0.136825 -0.390087 0.170259 +v 0.121093 -0.407592 0.178361 +v 0.131015 -0.417450 0.175660 +v 0.143841 -0.413626 0.170259 +v 0.146747 -0.399945 0.167559 +v 0.127304 -0.394642 0.182087 +v 0.138413 -0.391331 0.177409 +v 0.124789 -0.406490 0.184426 +v 0.133381 -0.415028 0.182087 +v 0.144489 -0.411716 0.177409 +v 0.147005 -0.399868 0.175071 +v 0.142925 -0.416844 0.168223 +v 0.147347 -0.422416 0.159714 +v 0.134310 -0.420161 0.173364 +v 0.130117 -0.429049 0.169996 +v 0.134539 -0.434621 0.161487 +v 0.143154 -0.431304 0.156346 +v 0.146398 -0.417981 0.172877 +v 0.151504 -0.424414 0.163052 +v 0.136450 -0.421811 0.178814 +v 0.131609 -0.432074 0.174924 +v 0.136715 -0.438507 0.165099 +v 0.146663 -0.434677 0.159163 +v 0.148573 -0.421867 0.176490 +v 0.152996 -0.427439 0.167980 +v 0.139958 -0.425184 0.181631 +v 0.135766 -0.434073 0.178262 +v 0.140188 -0.439644 0.169753 +v 0.148803 -0.436327 0.164612 +v 0.157642 -0.388504 0.145063 +v 0.134850 -0.380518 0.146416 +v 0.135327 -0.376442 0.163625 +v 0.161176 -0.385499 0.162091 +v 0.111678 -0.387425 0.146416 +v 0.109047 -0.384276 0.163625 +v 0.096977 -0.406587 0.145063 +v 0.092375 -0.406007 0.162091 +v 0.096363 -0.430684 0.142874 +v 0.091678 -0.433336 0.159608 +v 0.110070 -0.450512 0.140685 +v 0.107224 -0.455824 0.157125 +v 0.132863 -0.458497 0.139332 +v 0.133073 -0.464880 0.155591 +v 0.156034 -0.451590 0.139332 +v 0.159353 -0.457047 0.155591 +v 0.170735 -0.432429 0.140685 +v 0.176025 -0.435316 0.157125 +v 0.171349 -0.408332 0.142874 +v 0.176722 -0.407987 0.159608 +v 0.134324 -0.489546 0.136364 +v 0.122222 -0.488295 0.136364 +v 0.121089 -0.484495 0.159136 +v 0.136210 -0.486058 0.159136 +v 0.112779 -0.495968 0.136364 +v 0.109291 -0.494082 0.159136 +v 0.111528 -0.508071 0.136364 +v 0.107728 -0.509203 0.159136 +v 0.119201 -0.517513 0.136364 +v 0.117315 -0.521001 0.159136 +v 0.131304 -0.518764 0.136364 +v 0.132436 -0.522564 0.159136 +v 0.140746 -0.511091 0.136364 +v 0.144234 -0.512977 0.159136 +v 0.140746 -0.511091 0.136364 +v 0.141997 -0.498988 0.136364 +v 0.145797 -0.497856 0.159136 +v 0.144234 -0.512977 0.159136 +v 0.136612 -0.485314 0.181759 +v 0.120847 -0.483685 0.181759 +v 0.122669 -0.489795 0.181759 +v 0.133580 -0.490923 0.181759 +v 0.108547 -0.493680 0.181759 +v 0.114156 -0.496712 0.181759 +v 0.106918 -0.509445 0.181759 +v 0.113028 -0.507623 0.181759 +v 0.116913 -0.521745 0.181759 +v 0.119946 -0.516136 0.181759 +v 0.132678 -0.523374 0.181759 +v 0.130857 -0.517264 0.181759 +v 0.144978 -0.513379 0.181759 +v 0.139370 -0.510347 0.181759 +v 0.144978 -0.513379 0.181759 +v 0.146608 -0.497614 0.181759 +v 0.140497 -0.499435 0.181759 +v 0.139370 -0.510347 0.181759 +v 0.133580 -0.490923 0.181759 +v 0.122669 -0.489795 0.181759 +v 0.127796 -0.493533 0.145224 +v 0.139370 -0.510347 0.181759 +v 0.136759 -0.504563 0.145224 +v 0.024037 -0.395128 0.120296 +v 0.083348 -0.389559 0.111105 +v 0.083348 -0.389559 0.148428 +v 0.024037 -0.395128 0.154833 +v 0.075052 -0.361303 0.148428 +v 0.022894 -0.359032 0.154833 +v 0.075052 -0.361303 0.108060 +v 0.022894 -0.359032 0.120296 +v 0.238693 -0.366589 0.117332 +v 0.226012 -0.326800 0.117332 +v 0.226012 -0.326800 0.157797 +v 0.238693 -0.366589 0.157797 +v 0.022894 -0.359032 0.120296 +v 0.024037 -0.395128 0.120296 +v 0.169733 -0.346062 0.154924 +v 0.226012 -0.326800 0.117332 +v 0.169733 -0.346062 0.111105 +v 0.177267 -0.377689 0.154924 +v 0.177267 -0.377689 0.111105 +v 0.238693 -0.366589 0.117332 +v 0.122523 -0.354845 0.152636 +v 0.122523 -0.354845 0.109432 +v 0.130917 -0.383064 0.111105 +v 0.130917 -0.383064 0.152636 +v 0.182380 -0.367513 -0.004393 +v 0.096051 -0.381670 0.128785 +v 0.043002 -0.390739 0.128785 +v 0.139849 -0.374784 -0.004393 +v 0.095970 -0.359515 0.128785 +v 0.182295 -0.344155 -0.004393 +v 0.139764 -0.351426 -0.004393 +v 0.042922 -0.368585 0.128785 +v 0.139849 -0.374784 -0.004393 +v 0.043002 -0.390739 0.128785 +v 0.058300 -0.384473 -0.004393 +v 0.101109 -0.378134 -0.004393 +v 0.198565 -0.363244 0.128785 +v 0.145170 -0.371151 0.128785 +v 0.144384 -0.354426 0.128785 +v 0.197779 -0.346518 0.128785 +v 0.100281 -0.360500 -0.004393 +v 0.057472 -0.366839 -0.004393 +v 0.058300 -0.384473 -0.004393 +v 0.145170 -0.371151 0.128785 +v 0.199045 -0.532142 -0.004393 +v 0.112737 -0.546372 0.128785 +v 0.059702 -0.555484 0.128785 +v 0.156524 -0.539447 -0.004393 +v 0.112698 -0.524359 0.128785 +v 0.199004 -0.508932 -0.004393 +v 0.156483 -0.516238 -0.004393 +v 0.059663 -0.533471 0.128785 +v 0.156524 -0.539447 -0.004393 +v 0.059702 -0.555484 0.128785 +v 0.075073 -0.549465 -0.004393 +v 0.117825 -0.542935 -0.004393 +v 0.215155 -0.527625 0.128785 +v 0.161831 -0.535770 0.128785 +v 0.161196 -0.519548 0.128785 +v 0.214520 -0.511403 0.128785 +v 0.117155 -0.525831 -0.004393 +v 0.074403 -0.532362 -0.004393 +v 0.075073 -0.549465 -0.004393 +v 0.161831 -0.535770 0.128785 +v 0.358559 -0.295684 0.061267 +v 0.316431 -0.280847 0.061267 +v 0.316229 -0.289091 0.085495 +v 0.353550 -0.302235 0.085495 +v 0.273627 -0.293606 0.061267 +v 0.278310 -0.300394 0.085495 +v 0.246498 -0.329087 0.061267 +v 0.254276 -0.331826 0.085495 +v 0.245405 -0.373738 0.061267 +v 0.253308 -0.371383 0.085495 +v 0.270766 -0.410504 0.061267 +v 0.275775 -0.403953 0.085495 +v 0.312894 -0.425341 0.061267 +v 0.313096 -0.417097 0.085495 +v 0.355698 -0.412583 0.061267 +v 0.351016 -0.405795 0.085495 +v 0.382827 -0.377101 0.061267 +v 0.375049 -0.374362 0.085495 +v 0.383920 -0.332450 0.061267 +v 0.376017 -0.334806 0.085495 +v 0.297152 -0.287135 -0.002659 +v 0.284143 -0.311192 -0.002659 +v 0.295893 -0.323178 0.066195 +v 0.303189 -0.309686 0.066195 +v 0.308200 -0.324200 -0.002659 +v 0.309385 -0.330474 0.066195 +v 0.308200 -0.324200 -0.002659 +v 0.321209 -0.300143 -0.002659 +v 0.316681 -0.316982 0.066195 +v 0.309385 -0.330474 0.066195 +v 0.243663 -0.375185 -0.002659 +v 0.268004 -0.387654 -0.002659 +v 0.280547 -0.375474 0.066195 +v 0.266895 -0.368481 0.066195 +v 0.280473 -0.363313 -0.002659 +v 0.287540 -0.361822 0.066195 +v 0.280473 -0.363313 -0.002659 +v 0.256132 -0.350844 -0.002659 +v 0.273888 -0.354829 0.066195 +v 0.287540 -0.361822 0.066195 +v 0.387992 -0.334543 -0.002659 +v 0.364867 -0.319941 -0.002659 +v 0.351462 -0.331729 0.066195 +v 0.364432 -0.339918 0.066195 +v 0.350266 -0.343067 -0.002659 +v 0.343273 -0.344699 0.066195 +v 0.350266 -0.343067 -0.002659 +v 0.373391 -0.357668 -0.002659 +v 0.356243 -0.352888 0.066195 +v 0.343273 -0.344699 0.066195 +v 0.335532 -0.420725 -0.002659 +v 0.348591 -0.396695 -0.002659 +v 0.336623 -0.384757 0.066195 +v 0.329298 -0.398234 0.066195 +v 0.324562 -0.383636 -0.002659 +v 0.323145 -0.377432 0.066195 +v 0.324562 -0.383636 -0.002659 +v 0.311503 -0.407666 -0.002659 +v 0.315821 -0.390910 0.066195 +v 0.323145 -0.377432 0.066195 +v 0.337313 -0.511268 -0.001527 +v 0.321777 -0.501458 -0.001527 +v 0.312771 -0.509377 0.044730 +v 0.321484 -0.514879 0.044730 +v 0.311967 -0.516995 -0.001527 +v 0.307269 -0.518091 0.044730 +v 0.311967 -0.516995 -0.001527 +v 0.327503 -0.526804 -0.001527 +v 0.315982 -0.523593 0.044730 +v 0.307269 -0.518091 0.044730 +v 0.317539 -0.485162 0.041420 +v 0.289236 -0.475194 0.041420 +v 0.289100 -0.480732 0.057697 +v 0.314173 -0.489563 0.057697 +v 0.260479 -0.483765 0.041420 +v 0.263625 -0.488326 0.057697 +v 0.242253 -0.507603 0.041420 +v 0.247479 -0.509443 0.057697 +v 0.241519 -0.537601 0.041420 +v 0.246828 -0.536018 0.057697 +v 0.258557 -0.562301 0.041420 +v 0.261922 -0.557900 0.057697 +v 0.286860 -0.572269 0.041420 +v 0.286996 -0.566730 0.057697 +v 0.315617 -0.563697 0.041420 +v 0.312471 -0.559137 0.057697 +v 0.333843 -0.539860 0.041420 +v 0.328617 -0.538020 0.057697 +v 0.334577 -0.509862 0.041420 +v 0.329268 -0.511445 0.057697 +v 0.240348 -0.538573 -0.001527 +v 0.256702 -0.546950 -0.001527 +v 0.265128 -0.538767 0.044730 +v 0.255956 -0.534068 0.044730 +v 0.265078 -0.530596 -0.001527 +v 0.269826 -0.529595 0.044730 +v 0.265078 -0.530596 -0.001527 +v 0.248725 -0.522220 -0.001527 +v 0.260655 -0.524897 0.044730 +v 0.269826 -0.529595 0.044730 +v 0.276284 -0.479418 -0.001527 +v 0.267544 -0.495580 -0.001527 +v 0.275438 -0.503633 0.044730 +v 0.280339 -0.494568 0.044730 +v 0.283706 -0.504320 -0.001527 +v 0.284502 -0.508535 0.044730 +v 0.283706 -0.504320 -0.001527 +v 0.292446 -0.488158 -0.001527 +v 0.289404 -0.499470 0.044730 +v 0.284502 -0.508535 0.044730 +v 0.302069 -0.569168 -0.001527 +v 0.310842 -0.553024 -0.001527 +v 0.302801 -0.545003 0.044730 +v 0.297881 -0.554057 0.044730 +v 0.294698 -0.544250 -0.001527 +v 0.293747 -0.540082 0.044730 +v 0.294698 -0.544250 -0.001527 +v 0.285925 -0.560394 -0.001527 +v 0.288826 -0.549137 0.044730 +v 0.293747 -0.540082 0.044730 +v 0.038421 -0.554150 0.112782 +v 0.098035 -0.559599 0.110861 +v 0.098035 -0.559599 0.150163 +v 0.038421 -0.554150 0.164664 +v 0.096671 -0.529226 0.150163 +v 0.039459 -0.511608 0.164664 +v 0.096671 -0.529226 0.107654 +v 0.039458 -0.511608 0.112782 +v 0.252342 -0.542666 0.111132 +v 0.253065 -0.503330 0.111132 +v 0.253065 -0.503330 0.166314 +v 0.252342 -0.542666 0.166314 +v 0.039458 -0.511608 0.112782 +v 0.038421 -0.554150 0.112782 +v 0.192004 -0.521330 0.157003 +v 0.253065 -0.503330 0.111132 +v 0.192004 -0.521330 0.110861 +v 0.194834 -0.554953 0.157003 +v 0.194834 -0.554953 0.110861 +v 0.252342 -0.542666 0.111132 +v 0.142571 -0.530412 0.154594 +v 0.142571 -0.530412 0.109098 +v 0.145344 -0.561827 0.110861 +v 0.145344 -0.561827 0.154594 +v -0.238997 0.577452 0.465286 +v -0.208140 0.592603 0.465286 +v -0.202793 0.587095 0.537060 +v -0.237909 0.569854 0.537060 +v -0.192081 0.622996 0.465286 +v -0.184518 0.621683 0.537060 +v -0.196954 0.657025 0.465286 +v -0.190064 0.660408 0.537060 +v -0.220898 0.681690 0.465286 +v -0.217312 0.688477 0.537060 +v -0.254767 0.687571 0.465286 +v -0.255855 0.695169 0.537060 +v -0.285624 0.672420 0.465286 +v -0.290971 0.677928 0.537060 +v -0.301683 0.642026 0.465286 +v -0.309246 0.643340 0.537060 +v -0.296810 0.607998 0.465286 +v -0.303700 0.604615 0.537060 +v -0.272866 0.583333 0.465286 +v -0.276452 0.576546 0.537060 +v -0.272866 0.583333 0.465286 +v -0.276452 0.576546 0.537060 +v -0.184518 0.621683 0.537060 +v -0.190064 0.660408 0.537060 +v -0.290971 0.677928 0.537060 +v -0.309246 0.643340 0.537060 +v -0.303700 0.604615 0.537060 +v -0.202793 0.587095 0.537060 +v -0.255855 0.695169 0.537060 +v -0.217312 0.688477 0.537060 +v -0.276452 0.576546 0.537060 +v -0.237909 0.569854 0.537060 +v -0.192081 0.622996 0.465286 +v -0.208140 0.592603 0.465286 +v -0.296810 0.607998 0.465286 +v -0.301683 0.642026 0.465286 +v -0.196954 0.657025 0.465286 +v -0.285624 0.672420 0.465286 +v -0.220898 0.681690 0.465286 +v -0.254767 0.687571 0.465286 +v -0.238997 0.577452 0.465286 +v -0.272866 0.583333 0.465286 +v -0.239613 0.581755 0.535867 +v -0.211168 0.595721 0.535866 +v -0.205640 0.590027 0.581732 +v -0.238488 0.573899 0.581732 +v -0.196364 0.623740 0.535866 +v -0.188545 0.622382 0.581732 +v -0.200856 0.655109 0.535866 +v -0.193733 0.658606 0.581732 +v -0.222929 0.677846 0.535867 +v -0.219222 0.684863 0.581732 +v -0.254151 0.683267 0.535867 +v -0.255276 0.691123 0.581732 +v -0.282596 0.669301 0.535867 +v -0.288124 0.674996 0.581732 +v -0.297400 0.641283 0.535867 +v -0.305219 0.642641 0.581732 +v -0.292908 0.609914 0.535867 +v -0.300031 0.606416 0.581732 +v -0.270835 0.587176 0.535867 +v -0.274543 0.580160 0.581732 +v -0.239613 0.581755 0.535867 +v -0.238488 0.573899 0.581732 +v -0.240848 0.594326 0.446762 +v -0.219771 0.604820 0.449198 +v -0.215916 0.600613 0.467826 +v -0.240580 0.588504 0.467826 +v -0.208728 0.625720 0.449198 +v -0.203081 0.624906 0.467826 +v -0.211936 0.649045 0.446762 +v -0.206976 0.652104 0.467826 +v -0.228170 0.665883 0.442820 +v -0.226113 0.671819 0.467826 +v -0.251229 0.669805 0.438878 +v -0.253184 0.676519 0.467826 +v -0.272305 0.659311 0.436441 +v -0.277848 0.664410 0.467826 +v -0.283348 0.638411 0.436441 +v -0.290683 0.640117 0.467826 +v -0.280140 0.615086 0.438878 +v -0.286788 0.612918 0.467826 +v -0.263906 0.598248 0.442820 +v -0.267651 0.593204 0.467826 +v -0.228634 0.597361 0.400502 +v -0.214664 0.605745 0.406961 +v -0.206615 0.620980 0.406961 +v -0.207561 0.637245 0.400502 +v -0.217141 0.648328 0.390050 +v -0.231696 0.649997 0.379598 +v -0.245666 0.641612 0.373139 +v -0.253715 0.626378 0.373139 +v -0.252769 0.610113 0.379598 +v -0.243189 0.599029 0.390050 +v -0.205890 0.594831 0.349725 +v -0.197550 0.600327 0.354526 +v -0.192495 0.609895 0.354526 +v -0.192654 0.619881 0.349725 +v -0.197968 0.626471 0.341957 +v -0.206406 0.627147 0.334188 +v -0.214746 0.621651 0.329387 +v -0.219801 0.612084 0.329387 +v -0.219641 0.602097 0.334188 +v -0.214328 0.595508 0.341957 +v -0.175231 0.591772 0.307707 +v -0.172703 0.596556 0.307707 +v -0.188148 0.617535 0.366699 +v -0.201265 0.592708 0.366699 +v -0.173591 0.600085 0.303093 +v -0.198080 0.638663 0.348718 +v -0.177007 0.598830 0.298478 +v -0.221131 0.634962 0.330738 +v -0.179534 0.594046 0.298478 +v -0.234249 0.610135 0.330738 +v -0.178647 0.590517 0.303093 +v -0.224316 0.589008 0.348718 +v -0.175231 0.591772 0.307707 +v -0.201265 0.592708 0.366699 +v -0.179534 0.594046 0.298478 +v -0.177007 0.598830 0.298478 +v -0.173591 0.600085 0.303093 +v -0.178647 0.590517 0.303093 +v -0.172703 0.596556 0.307707 +v -0.175231 0.591772 0.307707 +v -0.198080 0.638663 0.348718 +v -0.221131 0.634962 0.330738 +v -0.234249 0.610135 0.330738 +v -0.224316 0.589008 0.348718 +v -0.201265 0.592708 0.366699 +v -0.188148 0.617535 0.366699 +v -0.201484 -0.581366 0.465286 +v -0.194068 -0.547800 0.465286 +v -0.186427 -0.547059 0.537060 +v -0.194867 -0.585257 0.537060 +v -0.207797 -0.516285 0.465286 +v -0.202051 -0.511195 0.537060 +v -0.237428 -0.498859 0.465286 +v -0.235772 -0.491364 0.537060 +v -0.271643 -0.502177 0.465286 +v -0.274709 -0.495140 0.537060 +v -0.297373 -0.524973 0.465286 +v -0.303989 -0.521082 0.537060 +v -0.304789 -0.558539 0.465286 +v -0.312430 -0.559281 0.537060 +v -0.291060 -0.590054 0.465286 +v -0.296806 -0.595144 0.537060 +v -0.261429 -0.607480 0.465286 +v -0.263085 -0.614976 0.537060 +v -0.227214 -0.604162 0.465286 +v -0.224148 -0.611199 0.537060 +v -0.227214 -0.604162 0.465286 +v -0.224148 -0.611199 0.537060 +v -0.202051 -0.511195 0.537060 +v -0.235772 -0.491364 0.537060 +v -0.312430 -0.559281 0.537060 +v -0.296806 -0.595144 0.537060 +v -0.263085 -0.614976 0.537060 +v -0.186427 -0.547059 0.537060 +v -0.303989 -0.521082 0.537060 +v -0.274709 -0.495140 0.537060 +v -0.224148 -0.611199 0.537060 +v -0.194867 -0.585257 0.537060 +v -0.207797 -0.516285 0.465286 +v -0.194068 -0.547800 0.465286 +v -0.261429 -0.607480 0.465286 +v -0.291060 -0.590054 0.465286 +v -0.237428 -0.498859 0.465286 +v -0.304789 -0.558539 0.465286 +v -0.271643 -0.502177 0.465286 +v -0.297373 -0.524973 0.465286 +v -0.201484 -0.581366 0.465286 +v -0.227214 -0.604162 0.465286 +v -0.205231 -0.579162 0.535866 +v -0.198394 -0.548219 0.535866 +v -0.190495 -0.547453 0.581732 +v -0.198390 -0.583185 0.581732 +v -0.211050 -0.519168 0.535866 +v -0.205110 -0.513905 0.581732 +v -0.238366 -0.503103 0.535867 +v -0.236653 -0.495354 0.581732 +v -0.269907 -0.506163 0.535867 +v -0.273076 -0.498887 0.581732 +v -0.293626 -0.527177 0.535867 +v -0.300466 -0.523154 0.581732 +v -0.300463 -0.558120 0.535867 +v -0.308362 -0.558886 0.581732 +v -0.287807 -0.587172 0.535867 +v -0.293747 -0.592434 0.581732 +v -0.260491 -0.603236 0.535867 +v -0.262203 -0.610985 0.581732 +v -0.228950 -0.600177 0.535867 +v -0.225780 -0.607452 0.581732 +v -0.205231 -0.579162 0.535866 +v -0.198390 -0.583185 0.581732 +v -0.215824 -0.572282 0.446762 +v -0.210872 -0.549265 0.449198 +v -0.205179 -0.548878 0.467826 +v -0.211107 -0.575706 0.467826 +v -0.220313 -0.527594 0.449198 +v -0.216153 -0.523688 0.467826 +v -0.240541 -0.515546 0.446762 +v -0.239837 -0.509760 0.467826 +v -0.263829 -0.517723 0.442820 +v -0.267184 -0.512412 0.467826 +v -0.281283 -0.533295 0.438878 +v -0.287749 -0.530633 0.467826 +v -0.286234 -0.556312 0.436441 +v -0.293677 -0.557462 0.467826 +v -0.276793 -0.577983 0.436441 +v -0.282704 -0.582651 0.467826 +v -0.256566 -0.590031 0.438878 +v -0.259020 -0.596579 0.467826 +v -0.233278 -0.587853 0.442820 +v -0.231673 -0.593927 0.467826 +v -0.210575 -0.560845 0.400502 +v -0.208409 -0.544696 0.406961 +v -0.215290 -0.528901 0.406961 +v -0.228591 -0.519490 0.400502 +v -0.243229 -0.520060 0.390050 +v -0.253615 -0.530392 0.379598 +v -0.255781 -0.546540 0.373139 +v -0.248899 -0.562336 0.373139 +v -0.235599 -0.571747 0.379598 +v -0.220960 -0.571177 0.390050 +v -0.194405 -0.544650 0.349725 +v -0.193495 -0.534705 0.354526 +v -0.197817 -0.524784 0.354526 +v -0.205720 -0.518677 0.349725 +v -0.214185 -0.518717 0.341957 +v -0.219979 -0.524889 0.334188 +v -0.220889 -0.534835 0.329387 +v -0.216567 -0.544755 0.329387 +v -0.208664 -0.550862 0.334188 +v -0.200199 -0.550822 0.341957 +v -0.172883 -0.522602 0.307707 +v -0.175044 -0.517642 0.307707 +v -0.201075 -0.516619 0.366699 +v -0.189860 -0.542362 0.366699 +v -0.178355 -0.516133 0.303093 +v -0.223782 -0.511197 0.348718 +v -0.179506 -0.519586 0.298478 +v -0.235275 -0.531518 0.330738 +v -0.177345 -0.524546 0.298478 +v -0.224060 -0.557261 0.330738 +v -0.174033 -0.526055 0.303093 +v -0.201352 -0.562683 0.348718 +v -0.172883 -0.522602 0.307707 +v -0.189860 -0.542362 0.366699 +v -0.177345 -0.524546 0.298478 +v -0.179506 -0.519586 0.298478 +v -0.178355 -0.516133 0.303093 +v -0.174033 -0.526055 0.303093 +v -0.175044 -0.517642 0.307707 +v -0.172883 -0.522602 0.307707 +v -0.223782 -0.511197 0.348718 +v -0.235275 -0.531518 0.330738 +v -0.224060 -0.557261 0.330738 +v -0.201352 -0.562683 0.348718 +v -0.189860 -0.542362 0.366699 +v -0.201075 -0.516619 0.366699 +v -0.447426 -0.527494 -0.023026 +v -0.497054 -0.560465 -0.023026 +v -0.490319 -0.564554 0.140847 +v -0.443810 -0.533656 0.140847 +v -0.283243 -0.713133 -0.023026 +v -0.265381 -0.625157 -0.023026 +v -0.273208 -0.625179 0.140847 +v -0.289947 -0.712765 0.140847 +v -0.282737 -0.560465 -0.023026 +v -0.289472 -0.564554 0.140847 +v -0.332366 -0.527494 -0.023026 +v -0.335982 -0.533656 0.140847 +v -0.389896 -0.521362 -0.023026 +v -0.389896 -0.527909 0.140847 +v -0.348764 -0.573050 0.140847 +v -0.389896 -0.568791 0.140847 +v -0.389896 -0.577627 0.338447 +v -0.354534 -0.581289 0.338447 +v -0.318654 -0.592486 0.140847 +v -0.328648 -0.597997 0.338447 +v -0.307632 -0.632399 0.140847 +v -0.319173 -0.632311 0.338447 +v -0.318654 -0.700484 0.140847 +v -0.328648 -0.699983 0.338447 +v -0.431027 -0.573050 0.140847 +v -0.461138 -0.592486 0.140847 +v -0.451143 -0.597997 0.338447 +v -0.425257 -0.581289 0.338447 +v -0.461138 -0.592486 0.140847 +v -0.431027 -0.573050 0.140847 +v -0.307632 -0.632399 0.140847 +v -0.318654 -0.700484 0.140847 +v -0.318654 -0.592486 0.140847 +v -0.348764 -0.573050 0.140847 +v -0.389896 -0.568791 0.140847 +v -0.389896 -0.547006 0.921101 +v -0.334546 -0.552738 0.921101 +v -0.294027 -0.578892 0.921101 +v -0.279196 -0.632602 0.921101 +v -0.294027 -0.701718 0.921101 +v -0.460618 -0.632311 0.338447 +v -0.451143 -0.699983 0.338447 +v -0.485764 -0.701718 0.921101 +v -0.500595 -0.632602 0.921101 +v -0.485764 -0.578892 0.921101 +v -0.445245 -0.552738 0.921101 +v -0.294027 -0.701718 0.921101 +v -0.279196 -0.632602 0.921101 +v -0.500595 -0.632602 0.921101 +v -0.485764 -0.701718 0.921101 +v -0.485764 -0.578892 0.921101 +v -0.294027 -0.578892 0.921101 +v -0.447426 0.527494 -0.023026 +v -0.443810 0.533656 0.140847 +v -0.490319 0.564554 0.140847 +v -0.497054 0.560465 -0.023026 +v -0.283243 0.713133 -0.023026 +v -0.289947 0.712765 0.140847 +v -0.273208 0.625179 0.140847 +v -0.265381 0.625157 -0.023026 +v -0.289472 0.564554 0.140847 +v -0.282737 0.560465 -0.023026 +v -0.335982 0.533656 0.140847 +v -0.332366 0.527494 -0.023026 +v -0.389896 0.527909 0.140847 +v -0.389896 0.521362 -0.023026 +v -0.348764 0.573050 0.140847 +v -0.354534 0.581289 0.338447 +v -0.389896 0.577627 0.338447 +v -0.389896 0.568791 0.140847 +v -0.318654 0.592486 0.140847 +v -0.328648 0.597997 0.338447 +v -0.307632 0.632399 0.140847 +v -0.319173 0.632311 0.338447 +v -0.318654 0.700484 0.140847 +v -0.328648 0.699983 0.338447 +v -0.472159 0.632399 0.140847 +v -0.460618 0.632311 0.338447 +v -0.451143 0.699983 0.338447 +v -0.461138 0.700484 0.140744 +v -0.461138 0.592486 0.140847 +v -0.451143 0.597997 0.338447 +v -0.431027 0.573050 0.140847 +v -0.425257 0.581289 0.338447 +v -0.461138 0.592486 0.140847 +v -0.431027 0.573050 0.140847 +v -0.472159 0.632399 0.140847 +v -0.506584 0.625179 0.140744 +v -0.461138 0.700484 0.140744 +v -0.489845 0.712765 0.140744 +v -0.307632 0.632399 0.140847 +v -0.318654 0.700484 0.140847 +v -0.318654 0.592486 0.140847 +v -0.348764 0.573050 0.140847 +v -0.389896 0.568791 0.140847 +v -0.334546 0.552738 0.921101 +v -0.389896 0.547006 0.921101 +v -0.294027 0.578892 0.921101 +v -0.279196 0.632602 0.921101 +v -0.294027 0.701718 0.921101 +v -0.500595 0.632602 0.921101 +v -0.485764 0.701718 0.921101 +v -0.485764 0.578892 0.921101 +v -0.445245 0.552738 0.921101 +v -0.485764 0.701718 0.921101 +v -0.451143 0.699983 0.338447 +v -0.451143 0.699983 0.338447 +v -0.461138 0.700484 0.140744 +v -0.489845 0.712765 0.140744 +v -0.289947 0.712765 0.140847 +v -0.283243 0.713133 -0.023026 +v -0.496548 0.713133 -0.025216 +v -0.461138 0.700484 0.140744 +v -0.318654 0.700484 0.140847 +v -0.294027 0.701718 0.921101 +v -0.485764 0.701718 0.921101 +v -0.500595 0.632602 0.921101 +v -0.279196 0.632602 0.921101 +v -0.485764 0.578892 0.921101 +v -0.294027 0.578892 0.921101 +v 0.507325 -0.527494 -0.023026 +v 0.457697 -0.560465 -0.023026 +v 0.464432 -0.564554 0.140847 +v 0.510941 -0.533656 0.140847 +v 0.440341 -0.625157 -0.023026 +v 0.448167 -0.625179 0.140744 +v 0.458203 -0.713133 -0.023026 +v 0.464906 -0.712765 0.140744 +v 0.671508 -0.713133 -0.023026 +v 0.689370 -0.625157 -0.023026 +v 0.681543 -0.625179 0.140847 +v 0.664804 -0.712765 0.140847 +v 0.672014 -0.560465 -0.023026 +v 0.665279 -0.564554 0.140847 +v 0.622386 -0.527494 -0.023026 +v 0.618769 -0.533656 0.140847 +v 0.564855 -0.521362 -0.023026 +v 0.564855 -0.527909 0.140847 +v 0.605987 -0.573050 0.140847 +v 0.564855 -0.568791 0.140847 +v 0.564855 -0.577627 0.338447 +v 0.600217 -0.581289 0.338447 +v 0.636097 -0.592486 0.140847 +v 0.626103 -0.597997 0.338447 +v 0.647119 -0.632399 0.140847 +v 0.635578 -0.632311 0.338447 +v 0.636097 -0.700484 0.140847 +v 0.626103 -0.699983 0.338447 +v 0.482592 -0.632399 0.140847 +v 0.493613 -0.700484 0.140744 +v 0.503608 -0.699983 0.338447 +v 0.494133 -0.632311 0.338447 +v 0.493613 -0.592486 0.140847 +v 0.503608 -0.597997 0.338447 +v 0.523724 -0.573050 0.140847 +v 0.529494 -0.581289 0.338447 +v 0.493613 -0.592486 0.140847 +v 0.523724 -0.573050 0.140847 +v 0.482592 -0.632399 0.140847 +v 0.493613 -0.700484 0.140744 +v 0.647119 -0.632399 0.140847 +v 0.636097 -0.700484 0.140847 +v 0.636097 -0.592486 0.140847 +v 0.605987 -0.573050 0.140847 +v 0.564855 -0.568791 0.140847 +v 0.564855 -0.547006 0.921101 +v 0.620205 -0.552738 0.921101 +v 0.660724 -0.578892 0.921101 +v 0.675555 -0.632602 0.921101 +v 0.660724 -0.701718 0.921101 +v 0.468987 -0.701718 0.921101 +v 0.454156 -0.632602 0.921101 +v 0.468987 -0.578892 0.921101 +v 0.509506 -0.552738 0.921101 +v 0.660724 -0.701718 0.921101 +v 0.675555 -0.632602 0.921101 +v 0.454156 -0.632602 0.921101 +v 0.468987 -0.701718 0.921101 +v 0.468987 -0.578892 0.921101 +v 0.660724 -0.578892 0.921101 +v -0.287865 -0.712307 0.452463 +v -0.281151 -0.529473 0.452463 +v -0.281151 -0.529473 0.543283 +v -0.287865 -0.712307 0.543283 +v -0.297407 -0.711724 0.566384 +v -0.387829 -0.711724 0.566384 +v -0.387829 -0.711724 0.429362 +v -0.297407 -0.711724 0.429362 +v -0.292956 -0.504722 0.452463 +v -0.387502 -0.503862 0.452463 +v -0.387502 -0.503862 0.543283 +v -0.292956 -0.504722 0.543283 +v -0.484115 -0.711724 0.566384 +v -0.484115 -0.711724 0.429362 +v -0.484923 -0.504907 0.452463 +v -0.484923 -0.504907 0.543283 +v -0.291320 -0.536528 0.566384 +v -0.297407 -0.711724 0.566384 +v -0.302021 -0.514091 0.566384 +v -0.387725 -0.513312 0.566384 +v -0.476036 -0.514259 0.566384 +v -0.297407 -0.711724 0.429362 +v -0.291320 -0.536528 0.429362 +v -0.387725 -0.513312 0.429362 +v -0.476036 -0.514259 0.429362 +v -0.302021 -0.514091 0.429362 +v 0.665253 -0.712307 0.452463 +v 0.671967 -0.561513 0.452463 +v 0.671967 -0.561513 0.543283 +v 0.665253 -0.712307 0.543283 +v 0.655712 -0.711724 0.566384 +v 0.565289 -0.711724 0.566384 +v 0.459283 -0.712307 0.543283 +v 0.454486 -0.566306 0.543283 +v 0.454486 -0.566306 0.452463 +v 0.459283 -0.712307 0.452463 +v 0.565289 -0.711724 0.429362 +v 0.655712 -0.711724 0.429362 +v 0.660163 -0.536762 0.452463 +v 0.565616 -0.535902 0.452463 +v 0.565616 -0.535902 0.543283 +v 0.660163 -0.536762 0.543283 +v 0.469003 -0.711724 0.566384 +v 0.469003 -0.711724 0.429362 +v 0.468195 -0.536947 0.452463 +v 0.468195 -0.536947 0.543283 +v 0.661798 -0.568567 0.566384 +v 0.655712 -0.711724 0.566384 +v 0.469003 -0.711724 0.566384 +v 0.464654 -0.572912 0.566384 +v 0.651097 -0.546131 0.566384 +v 0.565393 -0.545352 0.566384 +v 0.477082 -0.546299 0.566384 +v 0.655712 -0.711724 0.429362 +v 0.661798 -0.568567 0.429362 +v 0.464654 -0.572912 0.429362 +v 0.469003 -0.711724 0.429362 +v 0.477082 -0.546299 0.429362 +v 0.565393 -0.545352 0.429362 +v 0.651097 -0.546131 0.429362 +v 0.565289 -0.711724 0.566384 +v 0.469003 -0.711724 0.566384 +v 0.469003 -0.711724 0.429362 +v 0.565289 -0.711724 0.429362 +v 0.655712 -0.711724 0.566384 +v 0.655712 -0.711724 0.429362 +v 0.459283 -0.712307 0.543283 +v 0.459283 -0.712307 0.452463 +v 0.665253 -0.712307 0.452463 +v 0.665253 -0.712307 0.543283 +v -0.287572 0.712307 0.452463 +v -0.287572 0.712307 0.543283 +v -0.280858 0.529473 0.543283 +v -0.280858 0.529473 0.452463 +v -0.297113 0.711724 0.566384 +v -0.387536 0.711724 0.566384 +v -0.387536 0.711724 0.429362 +v -0.297113 0.711724 0.429362 +v -0.292662 0.504722 0.452463 +v -0.292662 0.504722 0.543283 +v -0.387208 0.503862 0.543283 +v -0.387208 0.503862 0.452463 +v -0.483822 0.711724 0.566384 +v -0.483822 0.711724 0.429362 +v -0.484630 0.504907 0.543283 +v -0.484630 0.504907 0.452463 +v -0.297113 0.711724 0.566384 +v -0.291027 0.536528 0.566384 +v -0.301727 0.514091 0.566384 +v -0.387432 0.513312 0.566384 +v -0.475743 0.514259 0.566384 +v -0.291027 0.536528 0.429362 +v -0.297113 0.711724 0.429362 +v -0.475743 0.514259 0.429362 +v -0.387432 0.513312 0.429362 +v -0.301727 0.514091 0.429362 +v -0.387536 0.711724 0.566384 +v -0.387536 0.711724 0.429362 +v -0.483822 0.711724 0.429362 +v -0.483822 0.711724 0.566384 +v -0.297113 0.711724 0.566384 +v -0.297113 0.711724 0.429362 +v -0.493542 0.712307 0.543283 +v -0.493542 0.712307 0.452463 +v -0.287572 0.712307 0.543283 +v -0.287572 0.712307 0.452463 +v -0.356935 -0.438596 0.390241 +v -0.392690 -0.438596 0.396251 +v -0.395843 -0.438596 0.414817 +v -0.357632 -0.438596 0.415676 +v -0.434913 -0.438596 0.315227 +v -0.453264 -0.438596 0.315227 +v -0.356935 -0.464081 0.390241 +v -0.357632 -0.464081 0.415676 +v -0.395843 -0.464081 0.414817 +v -0.392690 -0.464081 0.396251 +v -0.453264 -0.464081 0.315227 +v -0.434913 -0.464081 0.315227 +v -0.356935 -0.464081 0.390241 +v -0.392690 -0.464081 0.396251 +v -0.392690 -0.438596 0.396251 +v -0.356935 -0.438596 0.390241 +v -0.357632 -0.464081 0.415676 +v -0.357632 -0.438596 0.415676 +v -0.427770 -0.464081 0.394532 +v -0.427770 -0.438596 0.394532 +v -0.434913 -0.464081 0.315227 +v -0.434913 -0.438596 0.315227 +v -0.453264 -0.464081 0.315227 +v -0.453264 -0.438596 0.315227 +v -0.392400 -0.440706 0.397413 +v -0.370854 -0.440706 0.393686 +v -0.373061 -0.441905 0.362676 +v -0.393228 -0.441905 0.365543 +v -0.396582 -0.443950 0.335854 +v -0.408561 -0.443408 0.347089 +v -0.438395 -0.445899 0.322336 +v -0.438179 -0.443341 0.336146 +v -0.392400 -0.461971 0.397413 +v -0.393228 -0.460773 0.365543 +v -0.373061 -0.460773 0.362676 +v -0.370854 -0.461971 0.393686 +v -0.408561 -0.459496 0.347089 +v -0.396582 -0.458953 0.335854 +v -0.438179 -0.458676 0.336146 +v -0.438395 -0.456778 0.322336 +v -0.370854 -0.461971 0.393686 +v -0.373061 -0.460773 0.362676 +v -0.373061 -0.441905 0.362676 +v -0.370854 -0.440706 0.393686 +v -0.393228 -0.460773 0.365543 +v -0.392400 -0.461971 0.397413 +v -0.392400 -0.440706 0.397413 +v -0.393228 -0.441905 0.365543 +v -0.396582 -0.458953 0.335854 +v -0.396582 -0.443950 0.335854 +v -0.408561 -0.459496 0.347089 +v -0.408561 -0.443408 0.347089 +v -0.438395 -0.456778 0.322336 +v -0.438395 -0.445899 0.322336 +v -0.438179 -0.458676 0.336146 +v -0.438179 -0.443341 0.336146 +v -0.328552 -0.502376 0.407789 +v -0.328552 -0.365226 0.418121 +v -0.328552 -0.365226 0.440254 +v -0.328552 -0.502376 0.446882 +v -0.453264 -0.365226 0.440254 +v -0.453264 -0.502376 0.446882 +v -0.453264 -0.365226 0.418121 +v -0.453264 -0.502376 0.407789 +v -0.453264 -0.502376 0.407789 +v -0.453264 -0.365226 0.418121 +v -0.328552 -0.365226 0.418121 +v -0.328552 -0.502376 0.407789 +v -0.328552 -0.225458 0.407813 +v -0.453264 -0.225458 0.407813 +v -0.453264 -0.225458 0.446857 +v -0.328552 -0.225458 0.446857 +v -0.453264 -0.502376 0.407789 +v -0.328552 -0.502376 0.407789 +v -0.453264 -0.225458 0.407813 +v -0.453264 -0.225458 0.407813 +v -0.328552 -0.225458 0.407813 +v -0.328552 -0.225458 0.407813 +v -0.356935 -0.257355 0.390241 +v -0.392690 -0.257355 0.396251 +v -0.395843 -0.257355 0.414817 +v -0.357632 -0.257355 0.415676 +v -0.434913 -0.257355 0.315227 +v -0.453264 -0.257355 0.315227 +v -0.356935 -0.282839 0.390241 +v -0.357632 -0.282839 0.415676 +v -0.395843 -0.282839 0.414817 +v -0.392690 -0.282839 0.396251 +v -0.453264 -0.282839 0.315227 +v -0.434913 -0.282839 0.315227 +v -0.356935 -0.282839 0.390241 +v -0.392690 -0.282839 0.396251 +v -0.392690 -0.257355 0.396251 +v -0.356935 -0.257355 0.390241 +v -0.357632 -0.282839 0.415676 +v -0.357632 -0.257355 0.415676 +v -0.427770 -0.282839 0.394532 +v -0.427770 -0.257355 0.394532 +v -0.434913 -0.282839 0.315227 +v -0.434913 -0.257355 0.315227 +v -0.453264 -0.282839 0.315227 +v -0.453264 -0.257355 0.315227 +v -0.392400 -0.259464 0.397413 +v -0.370854 -0.259464 0.393686 +v -0.373061 -0.260663 0.362676 +v -0.393228 -0.260663 0.365543 +v -0.396582 -0.262709 0.335854 +v -0.408561 -0.262166 0.347089 +v -0.438395 -0.264658 0.322336 +v -0.438179 -0.262099 0.336146 +v -0.392400 -0.280730 0.397413 +v -0.393228 -0.279531 0.365543 +v -0.373061 -0.279531 0.362676 +v -0.370854 -0.280730 0.393686 +v -0.408561 -0.278254 0.347089 +v -0.396582 -0.277712 0.335854 +v -0.438179 -0.277435 0.336146 +v -0.438395 -0.275536 0.322336 +v -0.370854 -0.280730 0.393686 +v -0.373061 -0.279531 0.362676 +v -0.373061 -0.260663 0.362676 +v -0.370854 -0.259464 0.393686 +v -0.393228 -0.279531 0.365543 +v -0.392400 -0.280730 0.397413 +v -0.392400 -0.259464 0.397413 +v -0.393228 -0.260663 0.365543 +v -0.396582 -0.277712 0.335854 +v -0.396582 -0.262709 0.335854 +v -0.408561 -0.278254 0.347089 +v -0.408561 -0.262166 0.347089 +v -0.438395 -0.275536 0.322336 +v -0.438395 -0.264658 0.322336 +v -0.438179 -0.277435 0.336146 +v -0.438179 -0.262099 0.336146 +v -0.356935 0.277542 0.392889 +v -0.392690 0.277542 0.397847 +v -0.395843 0.277542 0.413165 +v -0.357632 0.277542 0.413874 +v -0.434913 0.277542 0.331001 +v -0.453264 0.277542 0.331001 +v -0.356935 0.250185 0.392889 +v -0.357632 0.250185 0.413874 +v -0.395843 0.250185 0.413165 +v -0.392690 0.250185 0.397847 +v -0.453264 0.250185 0.331001 +v -0.434913 0.250185 0.331001 +v -0.356935 0.250185 0.392889 +v -0.392690 0.250185 0.397847 +v -0.392690 0.277542 0.397847 +v -0.356935 0.277542 0.392889 +v -0.357632 0.250185 0.413874 +v -0.357632 0.277542 0.413874 +v -0.427770 0.250185 0.396429 +v -0.427770 0.277542 0.396429 +v -0.434913 0.250185 0.331001 +v -0.434913 0.277542 0.331001 +v -0.453264 0.250185 0.331001 +v -0.453264 0.277542 0.331001 +v -0.392400 0.275278 0.398806 +v -0.370854 0.275278 0.395731 +v -0.373061 0.273991 0.370147 +v -0.393228 0.273991 0.372512 +v -0.396582 0.271795 0.348019 +v -0.408561 0.272377 0.357288 +v -0.438395 0.269702 0.336866 +v -0.438179 0.272449 0.348260 +v -0.392400 0.252449 0.398806 +v -0.393228 0.253736 0.372512 +v -0.373061 0.253736 0.370147 +v -0.370854 0.252449 0.395731 +v -0.408561 0.255107 0.357288 +v -0.396582 0.255689 0.348019 +v -0.438179 0.255986 0.348260 +v -0.438395 0.258025 0.336866 +v -0.370854 0.252449 0.395731 +v -0.373061 0.253736 0.370147 +v -0.373061 0.273991 0.370147 +v -0.370854 0.275278 0.395731 +v -0.393228 0.253736 0.372512 +v -0.392400 0.252449 0.398806 +v -0.392400 0.275278 0.398806 +v -0.393228 0.273991 0.372512 +v -0.396582 0.255689 0.348019 +v -0.396582 0.271795 0.348019 +v -0.408561 0.255107 0.357288 +v -0.408561 0.272377 0.357288 +v -0.438395 0.258025 0.336866 +v -0.438395 0.269702 0.336866 +v -0.438179 0.255986 0.348260 +v -0.438179 0.272449 0.348260 +v -0.328552 0.231556 0.407366 +v -0.328552 0.361479 0.415891 +v -0.328552 0.361479 0.434151 +v -0.328552 0.231556 0.439620 +v -0.453264 0.361479 0.434151 +v -0.453264 0.231556 0.439620 +v -0.453264 0.361479 0.415891 +v -0.453264 0.231556 0.407366 +v -0.453264 0.231556 0.407366 +v -0.453264 0.361479 0.415891 +v -0.328552 0.361479 0.415891 +v -0.328552 0.231556 0.407366 +v -0.328552 0.493882 0.407387 +v -0.453264 0.493882 0.407387 +v -0.453264 0.493882 0.439599 +v -0.328552 0.493882 0.439599 +v -0.453264 0.231556 0.407366 +v -0.328552 0.231556 0.407366 +v -0.453264 0.493882 0.407387 +v -0.453264 0.493882 0.407387 +v -0.328552 0.493882 0.407387 +v -0.328552 0.493882 0.407387 +v -0.356935 0.462431 0.392889 +v -0.392690 0.462431 0.397847 +v -0.395843 0.462431 0.413165 +v -0.357632 0.462431 0.413874 +v -0.434913 0.462431 0.331001 +v -0.453264 0.462431 0.331001 +v -0.356935 0.435082 0.392889 +v -0.357632 0.435082 0.413874 +v -0.395843 0.435082 0.413165 +v -0.392690 0.435082 0.397847 +v -0.453264 0.435082 0.331001 +v -0.434913 0.435082 0.331001 +v -0.356935 0.435082 0.392889 +v -0.392690 0.435082 0.397847 +v -0.392690 0.462431 0.397847 +v -0.356935 0.462431 0.392889 +v -0.357632 0.435082 0.413874 +v -0.357632 0.462431 0.413874 +v -0.427770 0.435082 0.396429 +v -0.427770 0.462431 0.396429 +v -0.434913 0.435082 0.331001 +v -0.434913 0.462431 0.331001 +v -0.453264 0.435082 0.331001 +v -0.453264 0.462431 0.331001 +v -0.392400 0.460167 0.398806 +v -0.370854 0.460167 0.395731 +v -0.373061 0.458881 0.370147 +v -0.393228 0.458881 0.372512 +v -0.396582 0.456685 0.348019 +v -0.408561 0.457267 0.357288 +v -0.438395 0.454593 0.336866 +v -0.438179 0.457339 0.348260 +v -0.392400 0.437346 0.398806 +v -0.393228 0.438632 0.372512 +v -0.373061 0.438632 0.370147 +v -0.370854 0.437346 0.395731 +v -0.408561 0.440002 0.357288 +v -0.396582 0.440584 0.348019 +v -0.438179 0.440881 0.348260 +v -0.438395 0.442919 0.336866 +v -0.370854 0.437346 0.395731 +v -0.373061 0.438632 0.370147 +v -0.373061 0.458881 0.370147 +v -0.370854 0.460167 0.395731 +v -0.393228 0.438632 0.372512 +v -0.392400 0.437346 0.398806 +v -0.392400 0.460167 0.398806 +v -0.393228 0.458881 0.372512 +v -0.396582 0.440584 0.348019 +v -0.396582 0.456685 0.348019 +v -0.408561 0.440002 0.357288 +v -0.408561 0.457267 0.357288 +v -0.438395 0.442919 0.336866 +v -0.438395 0.454593 0.336866 +v -0.438179 0.440881 0.348260 +v -0.438179 0.457339 0.348260 +v -0.295252 0.181062 0.154481 +v -0.304289 0.171602 0.154481 +v -0.298081 0.168869 0.208830 +v -0.291829 0.175414 0.208830 +v -0.305693 0.158160 0.154481 +v -0.299052 0.159568 0.208830 +v -0.298927 0.145868 0.154481 +v -0.294372 0.151064 0.208830 +v -0.286577 0.139423 0.154481 +v -0.285827 0.146605 0.208830 +v -0.273358 0.141286 0.154481 +v -0.276682 0.147894 0.208830 +v -0.273358 0.141286 0.154481 +v -0.264321 0.150746 0.154481 +v -0.270429 0.154439 0.208830 +v -0.276682 0.147894 0.208830 +v -0.262918 0.164188 0.154481 +v -0.269458 0.163739 0.208830 +v -0.269683 0.176480 0.154481 +v -0.274139 0.172243 0.208830 +v -0.282034 0.182925 0.154481 +v -0.282684 0.176703 0.208830 +v -0.269471 0.175511 0.230530 +v -0.263536 0.164246 0.225535 +v -0.265515 0.151991 0.221150 +v -0.274652 0.143427 0.219052 +v -0.274652 0.143427 0.219052 +v -0.287457 0.141825 0.220040 +v -0.299039 0.147797 0.223738 +v -0.304974 0.159062 0.228733 +v -0.302996 0.171317 0.233118 +v -0.293859 0.179881 0.235216 +v -0.281054 0.181483 0.234228 +v -0.299039 0.147797 0.223738 +v -0.287457 0.141825 0.220040 +v -0.269471 0.175511 0.230530 +v -0.281054 0.181483 0.234228 +v -0.271029 -0.172735 0.158262 +v -0.257922 -0.171859 0.158262 +v -0.260665 -0.165625 0.212836 +v -0.269733 -0.166231 0.212836 +v -0.247766 -0.162856 0.158262 +v -0.253638 -0.159396 0.212836 +v -0.244440 -0.149166 0.158262 +v -0.251338 -0.149924 0.212836 +v -0.249215 -0.136017 0.158262 +v -0.254641 -0.140827 0.212836 +v -0.260267 -0.128433 0.158262 +v -0.262288 -0.135580 0.212836 +v -0.260267 -0.128433 0.158262 +v -0.273374 -0.129309 0.158262 +v -0.271356 -0.136186 0.212836 +v -0.262288 -0.135580 0.212836 +v -0.283530 -0.138312 0.158262 +v -0.278383 -0.142415 0.212836 +v -0.286856 -0.152002 0.158262 +v -0.280684 -0.151887 0.212836 +v -0.282081 -0.165151 0.158262 +v -0.277380 -0.160984 0.212836 +v -0.286356 -0.151141 0.234626 +v -0.283112 -0.138774 0.229610 +v -0.273335 -0.131040 0.225208 +v -0.260761 -0.130895 0.223100 +v -0.260761 -0.130895 0.223100 +v -0.250192 -0.138393 0.224093 +v -0.245665 -0.150670 0.227806 +v -0.248910 -0.163037 0.232822 +v -0.258686 -0.170771 0.237225 +v -0.271260 -0.170916 0.239332 +v -0.281829 -0.163418 0.238339 +v -0.245665 -0.150670 0.227806 +v -0.250192 -0.138393 0.224093 +v -0.286356 -0.151141 0.234626 +v -0.281829 -0.163418 0.238339 +v -0.209010 0.173785 -0.000076 +v -0.209010 0.214239 -0.000076 +v -0.209010 0.234996 0.181010 +v -0.209010 0.187399 0.176927 +v -0.209010 0.187399 0.176927 +v -0.448328 0.234996 0.181010 +v -0.448328 0.187399 0.176928 +v -0.448328 0.214239 -0.000076 +v -0.448328 0.173785 -0.000076 +v -0.209010 0.173785 -0.000076 +v -0.209010 0.187399 0.176927 +v -0.448328 0.187399 0.176928 +v -0.209010 -0.181187 -0.000076 +v -0.209010 -0.194802 0.176927 +v -0.209010 -0.242399 0.181010 +v -0.209010 -0.221641 -0.000076 +v -0.209010 -0.194802 0.176927 +v -0.448328 -0.194802 0.176928 +v -0.448328 -0.242399 0.181010 +v -0.448328 -0.221641 -0.000076 +v -0.448328 -0.181187 -0.000076 +v -0.448328 -0.194802 0.176928 +v -0.209010 -0.194802 0.176927 +v -0.209010 -0.181187 -0.000076 +v -0.236799 -0.195177 0.159738 +v -0.236799 0.187208 0.160356 +v -0.449134 0.187208 0.160356 +v -0.449134 -0.195177 0.159738 +v -0.236799 -0.182929 0.000058 +v -0.236799 0.175948 0.000058 +v 0.016243 0.027980 0.016578 +v 0.009101 -0.030901 0.022569 +v -0.020266 -0.030428 0.084240 +v -0.010908 0.046722 0.076391 +v 0.038111 -0.082713 0.027840 +v 0.017745 -0.098317 0.091147 +v 0.092192 -0.107667 0.030378 +v 0.088606 -0.131013 0.094473 +v 0.150687 -0.096229 0.029215 +v 0.165252 -0.116026 0.092949 +v 0.191254 -0.052770 0.024793 +v 0.218405 -0.059083 0.087155 +v 0.198396 0.006111 0.018803 +v 0.227763 0.018068 0.079306 +v 0.169386 0.057923 0.013532 +v 0.189752 0.085957 0.072399 +v 0.115305 0.082876 0.010993 +v 0.118891 0.118652 0.069073 +v 0.056810 0.071439 0.012157 +v 0.042245 0.103666 0.070598 +v 0.016243 0.027980 0.016578 +v -0.010908 0.046722 0.076391 +v -0.007001 0.057504 0.200274 +v -0.016041 -0.017018 0.207856 +v 0.010006 -0.011885 0.207730 +v 0.017080 0.046433 0.201797 +v 0.020675 -0.082593 0.214527 +v 0.038739 -0.063202 0.212951 +v 0.089122 -0.114174 0.217740 +v 0.092303 -0.087916 0.215465 +v 0.163156 -0.099699 0.216267 +v 0.150239 -0.076588 0.214313 +v 0.214498 -0.044695 0.210672 +v 0.190417 -0.033544 0.209934 +v 0.223538 0.029827 0.203090 +v 0.197491 0.024774 0.204001 +v 0.186822 0.095402 0.196419 +v 0.168759 0.076091 0.198780 +v 0.118375 0.126983 0.193206 +v 0.115195 0.100805 0.196266 +v 0.044341 0.112508 0.194678 +v 0.057258 0.089477 0.197418 +v -0.007001 0.057504 0.200274 +v 0.017080 0.046433 0.201797 +v 0.010006 -0.015389 0.171680 +v 0.017080 0.043178 0.169198 +v 0.038739 -0.066924 0.173863 +v 0.092303 -0.091744 0.174915 +v 0.150239 -0.080368 0.174433 +v 0.179960 -0.032204 0.174535 +v 0.186180 0.019296 0.172353 +v 0.160915 0.064613 0.170433 +v 0.113813 0.086438 0.169508 +v 0.062868 0.076435 0.169932 +v 0.017080 0.043178 0.169198 +v 0.194491 0.097406 0.134516 +v 0.119725 0.131903 0.131007 +v 0.234597 0.025776 0.141804 +v 0.224723 -0.055626 0.150085 +v 0.168641 -0.115707 0.156198 +v 0.087772 -0.131519 0.157806 +v 0.013006 -0.097022 0.154297 +v -0.027100 -0.025393 0.147010 +v -0.017226 0.056010 0.138728 +v 0.038856 0.116091 0.132615 +v -0.017226 0.056010 0.138728 +v -0.172675 0.193249 -0.003610 +v -0.121540 0.097689 -0.003610 +v -0.121540 0.097689 0.061781 +v -0.172675 0.193249 0.061781 +v -0.165399 0.191045 0.069521 +v -0.119337 0.104966 0.069521 +v -0.073887 0.121388 0.069521 +v -0.126520 0.219748 0.069521 +v -0.077115 0.244384 0.061781 +v -0.025980 0.148824 0.061781 +v -0.025980 0.148824 -0.003610 +v -0.077115 0.244384 -0.003610 +v -0.071085 0.115920 -0.003610 +v -0.071085 0.115920 0.061781 +v -0.129515 0.225112 -0.003610 +v -0.172675 0.193249 -0.003610 +v -0.172675 0.193249 0.061781 +v -0.129515 0.225112 0.061781 +v -0.119337 0.104966 0.069521 +v -0.165399 0.191045 0.069521 +v -0.073887 0.121388 0.069521 +v -0.079319 0.237108 0.069521 +v -0.033257 0.151028 0.069521 +v -0.165399 0.191045 0.069521 +v -0.126520 0.219748 0.069521 +v -0.033257 0.151028 0.069521 +v -0.079319 0.237108 0.069521 +v -0.060320 0.218895 0.048261 +v -0.059301 0.148140 0.048261 +v 0.084736 0.150214 0.054316 +v 0.083717 0.220970 0.054316 +v -0.059301 0.148140 -0.006293 +v 0.084736 0.150214 -0.006293 +v 0.083717 0.220970 -0.006293 +v -0.060320 0.218895 -0.006293 +v 0.200144 0.151877 -0.006293 +v 0.200144 0.151877 0.048261 +v 0.199125 0.222632 0.048261 +v 0.199125 0.222632 -0.006293 +v 0.281552 0.153049 -0.006293 +v 0.281552 0.153049 0.048261 +v 0.280533 0.223804 0.048261 +v 0.280533 0.223804 -0.006293 +v -0.144132 -0.191441 0.042900 +v -0.073372 -0.192034 0.042900 +v -0.072165 -0.047986 0.042900 +v -0.142925 -0.047393 0.042900 +v -0.073372 -0.192034 -0.051327 +v -0.072165 -0.047986 -0.051327 +v -0.142925 -0.047393 -0.051327 +v -0.144132 -0.191441 -0.051327 +v -0.071197 0.067430 -0.051327 +v -0.071197 0.067430 0.057953 +v -0.141957 0.068023 0.057953 +v -0.141958 0.068023 -0.051327 +v -0.070515 0.148843 -0.051327 +v -0.070515 0.148843 0.042900 +v -0.141275 0.149436 0.042900 +v -0.141275 0.149436 -0.051327 +v -0.057198 -0.271295 -0.003610 +v -0.035310 -0.165147 -0.003610 +v -0.035310 -0.165147 0.061781 +v -0.057198 -0.271295 0.061781 +v -0.061377 -0.264944 0.069521 +v -0.041661 -0.169327 0.069521 +v -0.087090 -0.152849 0.069521 +v -0.109619 -0.262107 0.069521 +v -0.163346 -0.249407 0.061781 +v -0.141458 -0.143260 0.061781 +v -0.141458 -0.143260 -0.003610 +v -0.163346 -0.249407 -0.003610 +v -0.085743 -0.146855 -0.003610 +v -0.085743 -0.146855 0.061781 +v -0.110753 -0.268145 -0.003610 +v -0.057198 -0.271295 -0.003610 +v -0.057198 -0.271295 0.061781 +v -0.110753 -0.268145 0.061781 +v -0.041661 -0.169327 0.069521 +v -0.061377 -0.264944 0.069521 +v -0.087090 -0.152849 0.069521 +v -0.156995 -0.245228 0.069521 +v -0.137278 -0.149610 0.069521 +v -0.061377 -0.264944 0.069521 +v -0.109619 -0.262107 0.069521 +v -0.137278 -0.149610 0.069521 +v -0.156995 -0.245228 0.069521 +v 0.261248 -0.259379 0.048261 +v 0.261248 -0.188616 0.048261 +v 0.117195 -0.188616 0.056181 +v 0.117195 -0.259379 0.056181 +v 0.261248 -0.188616 -0.006293 +v 0.117195 -0.188616 -0.006293 +v 0.117195 -0.259379 -0.006293 +v 0.261248 -0.259379 -0.006293 +v 0.001775 -0.188616 -0.006293 +v 0.001775 -0.188616 0.048261 +v 0.001775 -0.259379 0.048261 +v 0.001775 -0.259379 -0.006293 +v -0.079641 -0.188616 -0.006293 +v -0.079641 -0.188616 0.048261 +v -0.079641 -0.259379 0.048261 +v -0.079641 -0.259379 -0.006293 +v 0.374763 -0.185931 -0.003610 +v 0.284750 -0.125565 -0.003610 +v 0.284750 -0.125565 0.061780 +v 0.374763 -0.185931 0.061780 +v 0.367304 -0.187402 0.069521 +v 0.286220 -0.133024 0.069521 +v 0.253802 -0.168862 0.069521 +v 0.346452 -0.230997 0.069521 +v 0.314397 -0.275944 0.061780 +v 0.224384 -0.215578 0.061780 +v 0.224384 -0.215578 -0.003610 +v 0.314397 -0.275944 -0.003610 +v 0.248761 -0.165350 -0.003610 +v 0.248761 -0.165350 0.061780 +v 0.351615 -0.234327 -0.003610 +v 0.374763 -0.185931 -0.003610 +v 0.374763 -0.185931 0.061780 +v 0.351615 -0.234327 0.061780 +v 0.286220 -0.133024 0.069521 +v 0.367304 -0.187402 0.069521 +v 0.253802 -0.168862 0.069521 +v 0.312927 -0.268485 0.069521 +v 0.231843 -0.214108 0.069521 +v 0.367304 -0.187402 0.069521 +v 0.346452 -0.230997 0.069521 +v 0.231843 -0.214108 0.069521 +v 0.312927 -0.268485 0.069521 +v 0.360806 0.150027 0.048261 +v 0.290054 0.151260 0.048261 +v 0.287543 0.007230 0.058608 +v 0.358295 0.005996 0.058608 +v 0.290054 0.151260 -0.006293 +v 0.287543 0.007230 0.004054 +v 0.358295 0.005996 -0.006293 +v 0.360806 0.150027 -0.006293 +v 0.285531 -0.108173 -0.006293 +v 0.285531 -0.108173 0.048261 +v 0.356283 -0.109406 0.048261 +v 0.356283 -0.109406 -0.006293 +v 0.284112 -0.189577 -0.006293 +v 0.284112 -0.189577 0.048261 +v 0.354864 -0.190810 0.048261 +v 0.354864 -0.190810 -0.006293 +v 0.307878 0.254125 -0.003610 +v 0.231977 0.176759 -0.003610 +v 0.231977 0.176759 0.061780 +v 0.307878 0.254125 0.061780 +v 0.307951 0.246522 0.069521 +v 0.239579 0.176832 0.069521 +v 0.268838 0.138371 0.069521 +v 0.346963 0.218003 0.069521 +v 0.385244 0.178223 0.061780 +v 0.309342 0.100858 0.061780 +v 0.309342 0.100858 -0.003610 +v 0.385244 0.178223 -0.003610 +v 0.264458 0.134063 -0.003610 +v 0.264458 0.134063 0.061780 +v 0.351187 0.222465 -0.003610 +v 0.307878 0.254125 -0.003610 +v 0.307878 0.254125 0.061780 +v 0.351187 0.222465 0.061780 +v 0.239579 0.176832 0.069521 +v 0.307951 0.246522 0.069521 +v 0.268838 0.138371 0.069521 +v 0.377641 0.178151 0.069521 +v 0.309270 0.108460 0.069521 +v 0.307951 0.246522 0.069521 +v 0.346963 0.218003 0.069521 +v 0.309270 0.108460 0.069521 +v 0.377641 0.178151 0.069521 +v 0.289197 -0.197716 -0.005561 +v 0.289197 -0.016541 0.012898 +v 0.108022 -0.016541 0.042422 +v 0.108022 -0.197716 0.023963 +v 0.289197 0.164634 -0.005561 +v 0.108022 0.164634 0.023963 +v -0.073152 -0.016541 0.012898 +v -0.073152 -0.197716 -0.005561 +v -0.073152 0.164634 -0.005561 +v -0.209994 -0.341899 -0.009065 +v -0.264484 -0.366517 -0.009065 +v -0.254033 -0.377229 0.038065 +v -0.206483 -0.356542 0.037992 +v -0.297523 -0.412223 -0.009065 +v -0.283238 -0.416462 0.037848 +v -0.297523 -0.412223 -0.009065 +v -0.310382 -0.471676 -0.009065 +v -0.295111 -0.467860 0.037409 +v -0.283238 -0.416462 0.037848 +v -0.286947 -0.524863 -0.009065 +v -0.275435 -0.514262 0.036835 +v -0.238469 -0.557289 -0.009065 +v -0.233792 -0.542967 0.036308 +v -0.182978 -0.554253 -0.009065 +v -0.185636 -0.541007 0.036046 +v -0.122531 -0.540392 -0.009065 +v -0.133051 -0.529720 0.035850 +v -0.088189 -0.493503 -0.009065 +v -0.102702 -0.489477 0.036071 +v -0.090370 -0.435811 -0.009065 +v -0.103892 -0.439424 0.036573 +v -0.122120 -0.384463 -0.009065 +v -0.130800 -0.394515 0.037174 +v -0.155885 -0.346771 -0.009065 +v -0.159622 -0.361422 0.037670 +v -0.116660 -0.428039 0.076684 +v -0.108154 -0.472094 0.075302 +v -0.128929 -0.512213 0.076209 +v -0.173629 -0.530079 0.079306 +v -0.215812 -0.539039 0.082360 +v -0.256820 -0.519921 0.085794 +v -0.281088 -0.481866 0.088273 +v -0.278272 -0.434662 0.088848 +v -0.278272 -0.434662 0.088848 +v -0.258356 -0.395608 0.087988 +v -0.219497 -0.370189 0.085462 +v -0.177436 -0.367469 0.082313 +v -0.147084 -0.392389 0.079591 +v -0.173629 -0.530079 0.079306 +v -0.128929 -0.512213 0.076209 +v -0.147084 -0.392389 0.079591 +v -0.177436 -0.367469 0.082313 +v -0.215812 -0.539039 0.082360 +v -0.219497 -0.370189 0.085462 +v -0.256820 -0.519921 0.085794 +v -0.258356 -0.395608 0.087988 +v -0.278272 -0.434662 0.088848 +v -0.281088 -0.481866 0.088273 +v -0.108154 -0.472094 0.075302 +v -0.116660 -0.428039 0.076684 +v -0.338018 -0.423677 0.053237 +v -0.318740 -0.440080 0.053435 +v -0.250402 -0.338065 0.042896 +v -0.269680 -0.321661 0.042699 +v -0.315717 -0.443183 0.009001 +v -0.247379 -0.341168 -0.001538 +v -0.315717 -0.443183 0.009001 +v -0.360678 -0.412731 0.014417 +v -0.292340 -0.310715 0.003878 +v -0.247379 -0.341168 -0.001538 +v -0.360678 -0.412731 0.014417 +v -0.315717 -0.443183 0.009001 +v -0.318740 -0.440080 0.053435 +v -0.338018 -0.423677 0.053237 +v -0.250402 -0.338065 0.042896 +v -0.247379 -0.341168 -0.001538 +v -0.292340 -0.310715 0.003878 +v -0.269680 -0.321661 0.042699 +v -0.118395 -0.338441 0.043288 +v -0.140599 -0.350356 0.045689 +v -0.070189 -0.451504 0.045725 +v -0.047985 -0.439589 0.043323 +v -0.145943 -0.357635 0.001967 +v -0.075534 -0.458782 0.002003 +v -0.145943 -0.357635 0.001967 +v -0.101261 -0.326303 0.002064 +v -0.030851 -0.427450 0.002099 +v -0.075534 -0.458782 0.002003 +v -0.101261 -0.326303 0.002064 +v -0.145943 -0.357635 0.001967 +v -0.140599 -0.350356 0.045689 +v -0.118395 -0.338441 0.043288 +v -0.070189 -0.451504 0.045725 +v -0.075534 -0.458782 0.002003 +v -0.030851 -0.427450 0.002099 +v -0.047985 -0.439589 0.043323 +v -0.277106 -0.328473 0.277824 +v -0.281152 -0.314653 0.265830 +v -0.273795 -0.323654 0.257849 +v -0.270722 -0.334248 0.267450 +v -0.268716 -0.302614 0.261203 +v -0.264047 -0.314355 0.254117 +v -0.252234 -0.304396 0.268569 +v -0.251227 -0.315649 0.259986 +v -0.248188 -0.318217 0.280563 +v -0.248154 -0.326243 0.269588 +v -0.260623 -0.330255 0.285191 +v -0.257902 -0.335542 0.273319 +v -0.260623 -0.330255 0.285191 +v -0.257902 -0.335542 0.273319 +v -0.268716 -0.302614 0.261203 +v -0.281152 -0.314653 0.265830 +v -0.248188 -0.318217 0.280563 +v -0.252234 -0.304396 0.268569 +v -0.277106 -0.328473 0.277824 +v -0.260623 -0.330255 0.285191 +v -0.211114 -0.475743 0.139207 +v -0.212990 -0.467921 0.126615 +v -0.202887 -0.460094 0.121365 +v -0.190907 -0.460088 0.128707 +v -0.189031 -0.467910 0.141299 +v -0.199135 -0.475737 0.146549 +v -0.207478 -0.450190 0.136479 +v -0.199570 -0.450186 0.141325 +v -0.190907 -0.460088 0.128707 +v -0.202887 -0.460094 0.121365 +v -0.214147 -0.455356 0.139944 +v -0.212990 -0.467921 0.126615 +v -0.212908 -0.460519 0.148256 +v -0.211114 -0.475743 0.139207 +v -0.205001 -0.460515 0.153102 +v -0.199135 -0.475737 0.146549 +v -0.198332 -0.455349 0.149636 +v -0.189031 -0.467910 0.141299 +v -0.198332 -0.455349 0.149636 +v -0.189031 -0.467910 0.141299 +v -0.224371 -0.407874 0.162898 +v -0.209165 -0.407867 0.172217 +v -0.194547 -0.446504 0.138605 +v -0.208332 -0.446510 0.130157 +v -0.237195 -0.417809 0.169562 +v -0.224371 -0.407874 0.162898 +v -0.208332 -0.446510 0.130157 +v -0.219958 -0.455517 0.136198 +v -0.234814 -0.427737 0.185545 +v -0.217799 -0.464518 0.150689 +v -0.219608 -0.427730 0.194864 +v -0.204014 -0.464512 0.159137 +v -0.206784 -0.417795 0.188200 +v -0.192388 -0.455504 0.153095 +v -0.223516 -0.411554 0.169220 +v -0.214189 -0.411550 0.174937 +v -0.199570 -0.450186 0.141325 +v -0.207478 -0.450190 0.136479 +v -0.231383 -0.417648 0.173308 +v -0.223516 -0.411554 0.169220 +v -0.207478 -0.450190 0.136479 +v -0.214147 -0.455356 0.139944 +v -0.229922 -0.423739 0.183113 +v -0.212908 -0.460519 0.148256 +v -0.220595 -0.423734 0.188829 +v -0.205001 -0.460515 0.153102 +v -0.212728 -0.417640 0.184741 +v -0.198332 -0.455349 0.149636 +v -0.239133 -0.346084 0.227197 +v -0.236610 -0.355140 0.236868 +v -0.223200 -0.389784 0.204686 +v -0.225101 -0.382466 0.194937 +v -0.250760 -0.345233 0.221380 +v -0.235382 -0.382115 0.189179 +v -0.259865 -0.353438 0.225234 +v -0.243762 -0.389084 0.193170 +v -0.257343 -0.362493 0.234904 +v -0.241861 -0.396403 0.202919 +v -0.245716 -0.363344 0.240721 +v -0.231580 -0.396753 0.208677 +v -0.245716 -0.363344 0.240721 +v -0.231580 -0.396753 0.208677 +v -0.212728 -0.417640 0.184741 +v -0.214189 -0.411550 0.174937 +v -0.223516 -0.411554 0.169220 +v -0.231383 -0.417648 0.173308 +v -0.229922 -0.423739 0.183113 +v -0.220595 -0.423734 0.188829 +v -0.220595 -0.423734 0.188829 +v -0.205257 -0.423302 0.119740 +v -0.204920 -0.439538 0.148943 +v -0.201125 -0.448590 0.140748 +v -0.199633 -0.436491 0.106472 +v -0.219647 -0.404714 0.073808 +v -0.208057 -0.409914 0.092423 +v -0.230088 -0.378793 0.091297 +v -0.217357 -0.387118 0.109451 +v -0.212795 -0.405330 0.136058 +v -0.209754 -0.428022 0.159454 +v -0.220544 -0.386856 0.152829 +v -0.216201 -0.412651 0.173401 +v -0.224811 -0.430238 0.117889 +v -0.219187 -0.443426 0.104620 +v -0.214782 -0.453434 0.139455 +v -0.218577 -0.444382 0.147649 +v -0.217900 -0.441431 0.076654 +v -0.227611 -0.416850 0.090571 +v -0.236911 -0.394054 0.107599 +v -0.232349 -0.412265 0.134205 +v -0.223412 -0.432866 0.158161 +v -0.248037 -0.369666 0.142316 +v -0.240098 -0.393791 0.150977 +v -0.229858 -0.417495 0.172107 +v -0.214782 -0.453434 0.139455 +v -0.219187 -0.443426 0.104620 +v -0.240098 -0.393791 0.150977 +v -0.229858 -0.417495 0.172107 +v -0.505807 -0.090672 0.485693 +v -0.492953 -0.087538 0.490711 +v -0.495398 -0.067237 0.483847 +v -0.509081 -0.070573 0.478504 +v -0.479533 -0.087323 0.486421 +v -0.481111 -0.067007 0.479279 +v -0.470672 -0.090109 0.474459 +v -0.471679 -0.069973 0.466545 +v -0.469756 -0.094832 0.459396 +v -0.470704 -0.075001 0.450510 +v -0.477134 -0.099688 0.446984 +v -0.478558 -0.080171 0.437298 +v -0.489988 -0.102822 0.441965 +v -0.492241 -0.083507 0.431955 +v -0.503408 -0.103037 0.446257 +v -0.506527 -0.083736 0.436523 +v -0.512268 -0.100252 0.458218 +v -0.515960 -0.080771 0.449256 +v -0.513184 -0.095528 0.473281 +v -0.516935 -0.075743 0.465292 +v -0.513184 -0.095528 0.473281 +v -0.516935 -0.075743 0.465292 +v -0.479533 -0.087323 0.486421 +v -0.492953 -0.087538 0.490711 +v -0.512268 -0.100252 0.458218 +v -0.503408 -0.103037 0.446257 +v -0.470672 -0.090109 0.474459 +v -0.489988 -0.102822 0.441965 +v -0.469756 -0.094832 0.459396 +v -0.477134 -0.099688 0.446984 +v -0.505807 -0.090672 0.485693 +v -0.513184 -0.095528 0.473281 +v -0.496415 -0.108124 0.496376 +v -0.487577 -0.103249 0.498064 +v -0.493531 -0.086789 0.486667 +v -0.504471 -0.089457 0.482396 +v -0.477874 -0.102650 0.494509 +v -0.482109 -0.086607 0.483015 +v -0.471012 -0.106555 0.487069 +v -0.474568 -0.088977 0.472835 +v -0.469612 -0.113475 0.478586 +v -0.473789 -0.092997 0.460015 +v -0.474209 -0.120763 0.472299 +v -0.480068 -0.097130 0.449451 +v -0.483047 -0.125639 0.470611 +v -0.491007 -0.099798 0.445180 +v -0.492750 -0.126238 0.474166 +v -0.502429 -0.099981 0.448832 +v -0.499612 -0.122333 0.481607 +v -0.509971 -0.097610 0.459012 +v -0.501012 -0.115414 0.490090 +v -0.510750 -0.093590 0.471833 +v -0.501012 -0.115414 0.490090 +v -0.510750 -0.093590 0.471833 +v -0.483976 -0.125287 0.518942 +v -0.478848 -0.120178 0.516992 +v -0.471946 -0.119212 0.514011 +v -0.465906 -0.122758 0.511138 +v -0.463036 -0.129461 0.509470 +v -0.464431 -0.136762 0.509646 +v -0.469559 -0.141871 0.511596 +v -0.476461 -0.142836 0.514577 +v -0.482501 -0.139292 0.517450 +v -0.485372 -0.132588 0.519118 +v -0.485372 -0.132588 0.519118 +v -0.472899 -0.125898 0.540137 +v -0.470245 -0.122734 0.537626 +v -0.466032 -0.121980 0.535474 +v -0.461871 -0.123922 0.534503 +v -0.459350 -0.127819 0.535084 +v -0.459431 -0.132183 0.536995 +v -0.462086 -0.135347 0.539506 +v -0.466298 -0.136101 0.541657 +v -0.470459 -0.134159 0.542628 +v -0.472981 -0.130262 0.542047 +v -0.472981 -0.130262 0.542047 +v -0.461465 -0.119445 0.558424 +v -0.459359 -0.119068 0.557348 +v -0.464576 -0.119615 0.529437 +v -0.475506 -0.121575 0.535020 +v -0.457572 -0.120466 0.557980 +v -0.456291 -0.128455 0.529400 +v -0.457572 -0.120466 0.557980 +v -0.457890 -0.122242 0.559687 +v -0.458937 -0.139254 0.534948 +v -0.456291 -0.128455 0.529400 +v -0.459996 -0.122619 0.560762 +v -0.469867 -0.141213 0.540531 +v -0.461784 -0.121221 0.560131 +v -0.478152 -0.132373 0.540567 +v -0.459996 -0.122619 0.560762 +v -0.457890 -0.122242 0.559687 +v -0.457572 -0.120466 0.557980 +v -0.461784 -0.121221 0.560131 +v -0.459359 -0.119068 0.557348 +v -0.461465 -0.119445 0.558424 +v -0.456291 -0.128455 0.529400 +v -0.458937 -0.139254 0.534948 +v -0.469867 -0.141213 0.540531 +v -0.478152 -0.132373 0.540567 +v -0.475506 -0.121575 0.535020 +v -0.464576 -0.119615 0.529437 +v -0.509409 0.078652 0.480392 +v -0.512683 0.058553 0.473204 +v -0.499000 0.055217 0.478546 +v -0.496555 0.075518 0.485411 +v -0.484714 0.054988 0.473978 +v -0.483135 0.075303 0.481120 +v -0.475282 0.057953 0.461245 +v -0.474275 0.078089 0.469158 +v -0.474306 0.062981 0.445209 +v -0.473358 0.082812 0.454095 +v -0.482160 0.068151 0.431997 +v -0.480736 0.087668 0.441684 +v -0.495843 0.071487 0.426654 +v -0.493590 0.090802 0.436665 +v -0.510130 0.071716 0.431223 +v -0.507010 0.091018 0.440956 +v -0.519562 0.068751 0.443956 +v -0.515871 0.088232 0.452918 +v -0.520537 0.063723 0.459991 +v -0.516787 0.083508 0.467981 +v -0.516787 0.083508 0.467981 +v -0.520537 0.063723 0.459991 +v -0.483135 0.075303 0.481120 +v -0.507010 0.091018 0.440956 +v -0.515871 0.088232 0.452918 +v -0.496555 0.075518 0.485411 +v -0.474275 0.078089 0.469158 +v -0.493590 0.090802 0.436665 +v -0.473358 0.082812 0.454095 +v -0.480736 0.087668 0.441684 +v -0.516787 0.083508 0.467981 +v -0.509409 0.078652 0.480392 +v -0.500017 0.096104 0.491076 +v -0.508073 0.077437 0.477095 +v -0.497134 0.074770 0.481367 +v -0.491180 0.091229 0.492764 +v -0.485712 0.074586 0.477715 +v -0.481476 0.090630 0.489208 +v -0.478171 0.076957 0.467534 +v -0.474615 0.094536 0.481769 +v -0.477391 0.080978 0.454714 +v -0.473215 0.101455 0.473285 +v -0.483670 0.085110 0.444151 +v -0.477812 0.108743 0.466999 +v -0.494610 0.087778 0.439879 +v -0.486650 0.113619 0.465311 +v -0.506032 0.087961 0.443531 +v -0.496353 0.114218 0.468866 +v -0.513573 0.085590 0.453712 +v -0.503215 0.110313 0.476306 +v -0.514353 0.081570 0.466532 +v -0.504614 0.103394 0.484789 +v -0.504614 0.103394 0.484789 +v -0.514353 0.081570 0.466532 +v -0.482450 0.108159 0.511691 +v -0.487578 0.113267 0.513642 +v -0.475548 0.107192 0.508710 +v -0.469508 0.110738 0.505837 +v -0.466638 0.117441 0.504170 +v -0.468033 0.124742 0.504345 +v -0.473161 0.129851 0.506296 +v -0.480064 0.130816 0.509277 +v -0.486103 0.127272 0.512150 +v -0.488974 0.120568 0.513817 +v -0.488974 0.120568 0.513817 +v -0.473847 0.110715 0.532325 +v -0.476501 0.113878 0.534836 +v -0.469634 0.109960 0.530174 +v -0.465473 0.111902 0.529203 +v -0.462952 0.115799 0.529784 +v -0.463034 0.120163 0.531694 +v -0.465688 0.123327 0.534205 +v -0.469900 0.124081 0.536357 +v -0.474062 0.122139 0.537328 +v -0.476583 0.118243 0.536747 +v -0.476583 0.118243 0.536747 +v -0.465067 0.107425 0.553124 +v -0.479109 0.109555 0.529720 +v -0.468179 0.107595 0.524136 +v -0.462961 0.107048 0.552048 +v -0.459893 0.116435 0.524100 +v -0.461174 0.108446 0.552679 +v -0.461174 0.108446 0.552679 +v -0.459893 0.116435 0.524100 +v -0.462539 0.127234 0.529647 +v -0.461493 0.110222 0.554386 +v -0.473469 0.129193 0.535231 +v -0.463599 0.110599 0.555462 +v -0.481754 0.120353 0.535266 +v -0.465386 0.109201 0.554831 +v -0.463599 0.110599 0.555462 +v -0.465386 0.109201 0.554831 +v -0.461174 0.108446 0.552679 +v -0.461493 0.110222 0.554386 +v -0.462961 0.107048 0.552048 +v -0.465067 0.107425 0.553124 +v -0.459893 0.116435 0.524100 +v -0.481754 0.120353 0.535266 +v -0.473469 0.129193 0.535231 +v -0.462539 0.127234 0.529647 +v -0.468179 0.107595 0.524136 +v -0.479109 0.109555 0.529720 +v -0.264880 0.651371 0.580126 +v -0.265111 0.652853 0.613381 +v -0.238290 0.605908 0.601110 +v -0.251497 0.627397 0.557140 +v -0.255723 0.637583 0.644775 +v -0.244514 0.618396 0.652901 +v -0.263938 0.651914 0.580108 +v -0.250562 0.627936 0.557122 +v -0.237321 0.606467 0.601091 +v -0.264148 0.653408 0.613363 +v -0.243516 0.618971 0.652882 +v -0.254736 0.638152 0.644756 +v -0.230585 -0.562461 0.569611 +v -0.230227 -0.562564 0.596719 +v -0.271922 -0.549382 0.585481 +v -0.251390 -0.555889 0.550264 +v -0.244822 -0.557939 0.621864 +v -0.262247 -0.552426 0.627968 +v -0.230852 -0.563306 0.569611 +v -0.251655 -0.556727 0.550264 +v -0.272197 -0.550251 0.585481 +v -0.230500 -0.563428 0.596719 +v -0.262529 -0.553321 0.627968 +v -0.245101 -0.558824 0.621864 +v -0.278327 0.151241 0.236079 +v -0.278220 0.151048 0.252211 +v -0.290627 0.173922 0.245523 +v -0.284518 0.162654 0.224566 +v -0.277261 0.151819 0.236079 +v -0.283451 0.163233 0.224566 +v -0.289568 0.174496 0.245523 +v -0.277159 0.151623 0.252211 +v -0.269008 -0.142349 0.234220 +v -0.269087 -0.142211 0.245822 +v -0.259984 -0.158564 0.241012 +v -0.264466 -0.150508 0.225940 +v -0.269770 -0.142773 0.234220 +v -0.265229 -0.150933 0.225940 +v -0.260742 -0.158985 0.241012 +v -0.269845 -0.142633 0.245822 +v 0.127796 -0.493533 0.145224 +v 0.116767 -0.502496 0.145224 +v 0.125729 -0.513526 0.145224 +v 0.136759 -0.504563 0.145224 +v -0.575491 -0.721043 0.001923 +v -0.575491 -0.721043 0.001923 +v -0.114645 -0.721043 0.001923 +v 0.319177 -0.721043 0.001923 +v 0.741905 -0.721043 0.001923 +v 0.741905 -0.721043 0.001923 +v 0.464906 -0.712765 0.140744 +v -0.289947 -0.712765 0.140847 +v 0.664804 -0.712765 0.140847 +v 0.448167 -0.625179 0.140744 +v -0.273208 -0.625179 0.140847 +v 0.681543 -0.625179 0.140847 +v -0.185947 -0.618911 0.264762 +v -0.187438 -0.616327 0.314538 +v -0.330770 -0.616018 0.246266 +v -0.330770 -0.616018 0.316683 +v 0.075552 -0.615534 0.248478 +v 0.079190 -0.614170 0.299157 +v 0.359678 -0.611262 0.314656 +v 0.349125 -0.610981 0.262758 +v 0.510377 -0.608245 0.243607 +v 0.510377 -0.608245 0.322434 +v 0.086177 -0.601870 0.107347 +v 0.086177 -0.601870 0.152700 +v 0.086177 -0.601870 0.152700 +v 0.222300 -0.596550 0.152919 +v 0.293424 -0.596288 0.831581 +v -0.300480 -0.596288 0.851846 +v 0.058102 -0.596288 0.851846 +v 0.473809 -0.596288 0.851846 +v -0.157246 -0.596288 0.836647 +v 0.188087 -0.593086 0.152919 +v 0.188087 -0.593086 0.152919 +v 0.151411 -0.590904 0.160294 +v 0.118307 -0.589580 0.152700 +v 0.118307 -0.589580 0.152700 +v 0.063742 -0.588814 0.152700 +v 0.119065 -0.584458 0.160294 +v 0.119065 -0.584458 0.160294 +v -0.315313 -0.581021 0.366718 +v -0.321318 -0.581021 0.497917 +v 0.245786 -0.579001 0.152919 +v 0.245786 -0.579001 0.152919 +v 0.186388 -0.578329 0.160294 +v 0.186388 -0.578329 0.160294 +v -0.334654 -0.573301 0.608309 +v 0.118340 -0.572183 0.144772 +v 0.302069 -0.569168 -0.001527 +v 0.286996 -0.566730 0.057697 +v 0.055611 -0.565524 0.144772 +v -0.490319 -0.564554 0.140847 +v -0.289472 -0.564554 0.140847 +v 0.464432 -0.564554 0.140847 +v 0.665279 -0.564554 0.140847 +v -0.201352 -0.562683 0.348718 +v 0.145344 -0.561827 0.154594 +v 0.285925 -0.560394 -0.001527 +v 0.116544 -0.560137 0.149178 +v 0.098035 -0.559599 0.150163 +v 0.312471 -0.559137 0.057697 +v 0.261922 -0.557900 0.057697 +v -0.224060 -0.557261 0.330738 +v 0.184260 -0.556156 0.144899 +v 0.194834 -0.554953 0.157003 +v 0.038421 -0.554150 0.164664 +v 0.038421 -0.554150 0.164664 +v 0.184027 -0.554069 0.149178 +v 0.297881 -0.554057 0.044730 +v 0.310842 -0.553024 -0.001527 +v 0.288826 -0.549137 0.044730 +v 0.256702 -0.546950 -0.001527 +v 0.112737 -0.546372 0.128785 +v 0.477082 -0.546299 0.429362 +v 0.477082 -0.546299 0.566384 +v 0.651097 -0.546131 0.429362 +v 0.651097 -0.546131 0.566384 +v 0.302801 -0.545003 0.044730 +v 0.243482 -0.544751 0.144899 +v -0.290416 -0.544425 0.729348 +v 0.117825 -0.542935 -0.004393 +v 0.252342 -0.542666 0.166314 +v 0.252342 -0.542666 0.166314 +v -0.215812 -0.539039 0.082360 +v 0.265128 -0.538767 0.044730 +v 0.240348 -0.538573 -0.001527 +v 0.328617 -0.538020 0.057697 +v 0.468195 -0.536947 0.452463 +v 0.468195 -0.536947 0.543283 +v 0.660163 -0.536762 0.452463 +v 0.660163 -0.536762 0.543283 +v 0.246828 -0.536018 0.057697 +v 0.255956 -0.534068 0.044730 +v -0.443810 -0.533656 0.140847 +v -0.335982 -0.533656 0.140847 +v 0.510941 -0.533656 0.140847 +v 0.618769 -0.533656 0.140847 +v 0.059663 -0.533471 0.128785 +v 0.074403 -0.532362 -0.004393 +v 0.199045 -0.532142 -0.004393 +v -0.235275 -0.531518 0.330738 +v -0.458150 -0.531147 0.366718 +v -0.315313 -0.531147 0.366718 +v 0.142571 -0.530412 0.154594 +v 0.096671 -0.529226 0.150163 +v -0.389896 -0.527909 0.140847 +v 0.564855 -0.527909 0.140847 +v 0.215155 -0.527625 0.128785 +v 0.327503 -0.526804 -0.001527 +v -0.174033 -0.526055 0.303093 +v 0.117155 -0.525831 -0.004393 +v 0.260655 -0.524897 0.044730 +v -0.177345 -0.524546 0.298478 +v 0.112698 -0.524359 0.128785 +v 0.315982 -0.523593 0.044730 +v 0.132678 -0.523374 0.181759 +v 0.248725 -0.522220 -0.001527 +v 0.116913 -0.521745 0.181759 +v 0.192004 -0.521330 0.157003 +v -0.179506 -0.519586 0.298478 +v 0.161196 -0.519548 0.128785 +v -0.175044 -0.517642 0.307707 +v -0.201075 -0.516619 0.366699 +v 0.156483 -0.516238 -0.004393 +v -0.178355 -0.516133 0.303093 +v 0.321484 -0.514879 0.044730 +v -0.302021 -0.514091 0.429362 +v -0.302021 -0.514091 0.566384 +v 0.144978 -0.513379 0.181759 +v 0.144978 -0.513379 0.181759 +v -0.128929 -0.512213 0.076209 +v 0.039459 -0.511608 0.164664 +v 0.039459 -0.511608 0.164664 +v 0.329268 -0.511445 0.057697 +v 0.214520 -0.511403 0.128785 +v 0.337313 -0.511268 -0.001527 +v -0.223782 -0.511197 0.348718 +v 0.106918 -0.509445 0.181759 +v 0.247479 -0.509443 0.057697 +v 0.312771 -0.509377 0.044730 +v 0.199004 -0.508932 -0.004393 +v -0.292956 -0.504722 0.452463 +v -0.292956 -0.504722 0.543283 +v 0.275438 -0.503633 0.044730 +v 0.253065 -0.503330 0.166314 +v 0.253065 -0.503330 0.166314 +v -0.453264 -0.502376 0.446882 +v -0.453264 -0.502376 0.446882 +v -0.328552 -0.502376 0.446882 +v -0.328552 -0.502376 0.446882 +v 0.321777 -0.501458 -0.001527 +v 0.289404 -0.499470 0.044730 +v 0.146608 -0.497614 0.181759 +v 0.267544 -0.495580 -0.001527 +v 0.280339 -0.494568 0.044730 +v 0.108547 -0.493680 0.181759 +v 0.314173 -0.489563 0.057697 +v 0.263625 -0.488326 0.057697 +v 0.292446 -0.488158 -0.001527 +v 0.136612 -0.485314 0.181759 +v 0.120847 -0.483685 0.181759 +v -0.281088 -0.481866 0.088273 +v 0.289100 -0.480732 0.057697 +v 0.276284 -0.479418 -0.001527 +v -0.211114 -0.475743 0.139207 +v -0.199135 -0.475737 0.146549 +v -0.108154 -0.472094 0.075302 +v -0.244178 -0.471045 0.832424 +v -0.244178 -0.471045 0.832424 +v -0.212990 -0.467921 0.126615 +v 0.133073 -0.464880 0.155591 +v 0.105557 -0.464577 0.138312 +v -0.217799 -0.464518 0.150689 +v -0.217799 -0.464518 0.150689 +v -0.204014 -0.464512 0.159137 +v -0.204014 -0.464512 0.159137 +v -0.434913 -0.464081 0.315227 +v -0.356935 -0.464081 0.390241 +v -0.427770 -0.464081 0.394532 +v 0.105951 -0.463304 0.133906 +v -0.212908 -0.460519 0.148256 +v -0.205001 -0.460515 0.153102 +v -0.202887 -0.460094 0.121365 +v -0.190907 -0.460088 0.128707 +v 0.173755 -0.457917 0.138312 +v 0.047579 -0.457113 0.133906 +v 0.159353 -0.457047 0.155591 +v 0.107224 -0.455824 0.157125 +v -0.219958 -0.455517 0.136198 +v -0.219958 -0.455517 0.136198 +v -0.192388 -0.455504 0.153095 +v -0.192388 -0.455504 0.153095 +v -0.214147 -0.455356 0.139944 +v -0.070189 -0.451504 0.045725 +v 0.173953 -0.451037 0.134033 +v -0.207478 -0.450190 0.136479 +v -0.199570 -0.450186 0.141325 +v -0.201125 -0.448590 0.140748 +v -0.208332 -0.446510 0.130157 +v -0.208332 -0.446510 0.130157 +v -0.194547 -0.446504 0.138605 +v -0.194547 -0.446504 0.138605 +v -0.318740 -0.440080 0.053435 +v -0.047985 -0.439589 0.043323 +v 0.235011 -0.439582 0.134033 +v -0.434913 -0.438596 0.315227 +v -0.356935 -0.438596 0.390241 +v -0.427770 -0.438596 0.394532 +v 0.136715 -0.438507 0.165099 +v -0.207289 -0.436671 0.059471 +v -0.207289 -0.436671 0.059471 +v -0.199633 -0.436491 0.106472 +v -0.238969 -0.435946 0.791554 +v 0.176025 -0.435316 0.157125 +v 0.146663 -0.434677 0.159163 +v -0.198346 -0.434496 0.078506 +v 0.091678 -0.433336 0.159608 +v 0.131609 -0.432074 0.174924 +v 0.143154 -0.431304 0.156346 +v -0.234814 -0.427737 0.185545 +v -0.234814 -0.427737 0.185545 +v -0.219608 -0.427730 0.194864 +v -0.219608 -0.427730 0.194864 +v -0.030851 -0.427450 0.002099 +v 0.139958 -0.425184 0.181631 +v 0.151504 -0.424414 0.163052 +v -0.229922 -0.423739 0.183113 +v -0.338018 -0.423677 0.053237 +v 0.136450 -0.421811 0.178814 +v 0.335532 -0.420725 -0.002659 +v 0.146398 -0.417981 0.172877 +v -0.237195 -0.417809 0.169562 +v -0.237195 -0.417809 0.169562 +v -0.206784 -0.417795 0.188200 +v -0.206784 -0.417795 0.188200 +v -0.231383 -0.417648 0.173308 +v -0.212728 -0.417640 0.184741 +v 0.131015 -0.417450 0.175660 +v 0.313096 -0.417097 0.085495 +v 0.143841 -0.413626 0.170259 +v -0.360678 -0.412731 0.014417 +v -0.216201 -0.412651 0.173401 +v -0.223516 -0.411554 0.169220 +v -0.214189 -0.411550 0.174937 +v 0.176722 -0.407987 0.159608 +v -0.224371 -0.407874 0.162898 +v -0.224371 -0.407874 0.162898 +v -0.209165 -0.407867 0.172217 +v -0.209165 -0.407867 0.172217 +v 0.311503 -0.407666 -0.002659 +v 0.121093 -0.407592 0.178361 +v 0.124789 -0.406490 0.184426 +v 0.092375 -0.406007 0.162091 +v 0.351016 -0.405795 0.085495 +v -0.219647 -0.404714 0.073808 +v 0.275775 -0.403953 0.085495 +v 0.143051 -0.401047 0.161494 +v 0.146747 -0.399945 0.167559 +v 0.329298 -0.398234 0.066195 +v 0.348591 -0.396695 -0.002659 +v -0.241861 -0.396403 0.202919 +v 0.024037 -0.395128 0.154833 +v 0.024037 -0.395128 0.154833 +v 0.123998 -0.393911 0.175660 +v -0.426738 -0.391170 0.484555 +v 0.315821 -0.390910 0.066195 +v 0.136825 -0.390087 0.170259 +v -0.223200 -0.389784 0.204686 +v 0.083348 -0.389559 0.148428 +v -0.411402 -0.389488 0.456599 +v -0.243762 -0.389084 0.193170 +v 0.268004 -0.387654 -0.002659 +v -0.429029 -0.386891 0.517152 +v -0.220544 -0.386856 0.152829 +v 0.161176 -0.385499 0.162091 +v 0.336623 -0.384757 0.066195 +v 0.109047 -0.384276 0.163625 +v 0.130917 -0.383064 0.152636 +v -0.225101 -0.382466 0.194937 +v -0.387130 -0.382297 0.440775 +v -0.235382 -0.382115 0.189179 +v 0.096051 -0.381670 0.128785 +v -0.230088 -0.378793 0.091297 +v 0.101109 -0.378134 -0.004393 +v -0.417661 -0.377799 0.545656 +v 0.177267 -0.377689 0.154924 +v 0.135327 -0.376442 0.163625 +v 0.280547 -0.375474 0.066195 +v 0.243663 -0.375185 -0.002659 +v 0.375049 -0.374362 0.085495 +v -0.176609 -0.373210 0.900818 +v -0.176609 -0.373210 0.900818 +v -0.319472 -0.371829 0.496722 +v -0.360427 -0.371523 0.441323 +v -0.360427 -0.371523 0.441323 +v 0.253308 -0.371383 0.085495 +v 0.042922 -0.368585 0.128785 +v 0.266895 -0.368481 0.066195 +v 0.182380 -0.367513 -0.004393 +v -0.177436 -0.367469 0.082313 +v 0.057472 -0.366839 -0.004393 +v -0.326164 -0.366707 0.475067 +v 0.238693 -0.366589 0.157797 +v 0.238693 -0.366589 0.157797 +v -0.395681 -0.366329 0.562428 +v -0.453264 -0.365226 0.440254 +v -0.328552 -0.365226 0.440254 +v 0.198565 -0.363244 0.128785 +v -0.228483 -0.362730 0.144168 +v -0.257343 -0.362493 0.234904 +v 0.075052 -0.361303 0.148428 +v -0.322270 -0.360971 0.501280 +v 0.037224 -0.360889 0.144772 +v 0.100281 -0.360500 -0.004393 +v -0.338447 -0.360053 0.458095 +v 0.095970 -0.359515 0.128785 +v -0.176609 -0.359250 0.848096 +v 0.022894 -0.359032 0.154833 +v 0.022894 -0.359032 0.154833 +v 0.373391 -0.357668 -0.002659 +v -0.328962 -0.355849 0.479625 +v -0.368978 -0.355555 0.562976 +v -0.236610 -0.355140 0.236868 +v 0.122523 -0.354845 0.152636 +v 0.273888 -0.354829 0.066195 +v 0.144384 -0.354426 0.128785 +v -0.259865 -0.353438 0.225234 +v 0.356243 -0.352888 0.066195 +v 0.139764 -0.351426 -0.004393 +v 0.093980 -0.351118 0.149178 +v -0.327079 -0.350961 0.486599 +v 0.256132 -0.350844 -0.002659 +v -0.243401 -0.350576 0.137634 +v -0.243401 -0.350576 0.137634 +v -0.140599 -0.350356 0.045689 +v -0.316348 -0.350083 0.473251 +v -0.307422 -0.348749 0.487407 +v -0.344706 -0.348364 0.547152 +v -0.322320 -0.347066 0.478987 +v -0.329370 -0.346682 0.519196 +v 0.197779 -0.346518 0.128785 +v -0.239133 -0.346084 0.227197 +v 0.169733 -0.346062 0.154924 +v -0.313394 -0.345731 0.493142 +v -0.250760 -0.345233 0.221380 +v 0.034755 -0.344658 0.149888 +v 0.034755 -0.344658 0.149888 +v 0.093361 -0.344161 0.144772 +v 0.182295 -0.344155 -0.004393 +v -0.301320 -0.340945 0.474375 +v -0.301320 -0.340945 0.474375 +v -0.305419 -0.340295 0.476550 +v -0.313931 -0.340125 0.464035 +v -0.313931 -0.340125 0.464035 +v 0.364432 -0.339918 0.066195 +v -0.313042 -0.339289 0.470052 +v -0.304843 -0.338568 0.483644 +v -0.118395 -0.338441 0.043288 +v -0.300395 -0.338232 0.485780 +v -0.300395 -0.338232 0.485780 +v -0.250402 -0.338065 0.042896 +v 0.161651 -0.336739 0.149178 +v -0.318446 -0.335895 0.475271 +v -0.322877 -0.335294 0.472893 +v -0.322877 -0.335294 0.472893 +v -0.310247 -0.335173 0.488864 +v 0.161260 -0.334844 0.144899 +v 0.376017 -0.334806 0.085495 +v 0.387992 -0.334543 -0.002659 +v -0.270722 -0.334248 0.267450 +v -0.309387 -0.333431 0.494623 +v -0.309387 -0.333431 0.494623 +v -0.320643 -0.332003 0.487932 +v -0.320643 -0.332003 0.487932 +v 0.254276 -0.331826 0.085495 +v 0.351462 -0.331729 0.066195 +v 0.058206 -0.330401 0.149888 +v -0.277106 -0.328473 0.277824 +v 0.216432 -0.328119 0.144899 +v 0.226012 -0.326800 0.157797 +v 0.226012 -0.326800 0.157797 +v -0.101261 -0.326303 0.002064 +v -0.248154 -0.326243 0.269588 +v 0.089083 -0.325584 0.149888 +v 0.089083 -0.325584 0.149888 +v -0.273795 -0.323654 0.257849 +v -0.312300 -0.323448 0.460510 +v 0.295893 -0.323178 0.066195 +v -0.320385 -0.323012 0.469524 +v -0.269680 -0.321661 0.042699 +v -0.444034 -0.320509 0.473706 +v 0.364867 -0.319941 -0.002659 +v -0.300993 -0.319833 0.468534 +v 0.087578 -0.319203 0.156351 +v 0.087578 -0.319203 0.156351 +v -0.431639 -0.319164 0.451021 +v -0.307735 -0.318939 0.488458 +v -0.248188 -0.318217 0.280563 +v -0.300092 -0.317875 0.479089 +v -0.445872 -0.317026 0.500172 +v 0.316681 -0.316982 0.066195 +v -0.251227 -0.315649 0.259986 +v 0.156860 -0.315354 0.150074 +v 0.156860 -0.315354 0.150074 +v -0.281152 -0.314653 0.265830 +v -0.264047 -0.314355 0.254117 +v -0.412011 -0.313353 0.438195 +v 0.284143 -0.311192 -0.002659 +v -0.292340 -0.310715 0.003878 +v 0.303189 -0.309686 0.066195 +v -0.436663 -0.309650 0.523326 +v 0.212467 -0.309309 0.150074 +v 0.212467 -0.309309 0.150074 +v -0.327300 -0.307523 0.466312 +v 0.117136 -0.306193 0.156351 +v 0.183450 -0.306128 0.150074 +v 0.154635 -0.305797 0.156351 +v 0.154635 -0.305797 0.156351 +v -0.390407 -0.304632 0.438664 +v -0.390407 -0.304632 0.438664 +v -0.252234 -0.304396 0.268569 +v -0.321016 -0.303958 0.456112 +v -0.268716 -0.302614 0.261203 +v 0.353550 -0.302235 0.085495 +v 0.278310 -0.300394 0.085495 +v -0.418873 -0.300357 0.536965 +v 0.321209 -0.300143 -0.002659 +v -0.309153 -0.299285 0.464178 +v -0.315653 -0.299264 0.484534 +v -0.339477 -0.295782 0.463620 +v -0.339477 -0.295782 0.463620 +v -0.372617 -0.295339 0.452303 +v -0.451661 -0.295335 0.469319 +v -0.439918 -0.294061 0.447825 +v -0.336953 -0.292064 0.480608 +v -0.336953 -0.292064 0.480608 +v -0.453403 -0.292035 0.494395 +v -0.336269 -0.291807 0.465214 +v -0.397269 -0.291636 0.537434 +v 0.316229 -0.289091 0.085495 +v -0.333510 -0.288895 0.450943 +v -0.333510 -0.288895 0.450943 +v -0.421320 -0.288555 0.435672 +v -0.363407 -0.287963 0.475458 +v 0.297152 -0.287135 -0.002659 +v -0.332869 -0.287078 0.457267 +v -0.369719 -0.286886 0.469766 +v -0.377640 -0.285825 0.524608 +v -0.444677 -0.285046 0.516333 +v -0.365246 -0.284480 0.501923 +v -0.434913 -0.282839 0.315227 +v -0.356935 -0.282839 0.390241 +v -0.427770 -0.282839 0.394532 +v -0.329726 -0.282446 0.478837 +v -0.351822 -0.281886 0.466983 +v -0.351822 -0.281886 0.466983 +v -0.328405 -0.281374 0.485313 +v -0.328405 -0.281374 0.485313 +v -0.373319 -0.280824 0.463280 +v -0.400850 -0.280292 0.436117 +v -0.400850 -0.280292 0.436117 +v -0.350648 -0.280157 0.474883 +v -0.327140 -0.278937 0.462623 +v -0.326285 -0.277722 0.470906 +v -0.351947 -0.276974 0.461263 +v -0.323707 -0.276657 0.459579 +v -0.323707 -0.276657 0.459579 +v -0.370365 -0.276471 0.483171 +v -0.427821 -0.276241 0.529257 +v 0.314397 -0.275944 -0.003610 +v 0.314397 -0.275944 0.061780 +v -0.322363 -0.274522 0.472659 +v -0.349579 -0.273411 0.477077 +v -0.383994 -0.271487 0.449040 +v -0.373965 -0.270408 0.476684 +v -0.375147 -0.270390 0.471478 +v -0.350569 -0.269455 0.465282 +v 0.312927 -0.268485 0.069521 +v -0.349658 -0.268478 0.471374 +v -0.407352 -0.267978 0.529701 +v -0.375268 -0.264498 0.470978 +v -0.388754 -0.262472 0.517548 +v -0.377010 -0.261198 0.496054 +v -0.079641 -0.259379 0.048261 +v 0.001775 -0.259379 0.048261 +v 0.261248 -0.259379 0.048261 +v 0.117195 -0.259379 0.056181 +v -0.434913 -0.257355 0.315227 +v -0.356935 -0.257355 0.390241 +v -0.427770 -0.257355 0.394532 +v -0.447635 -0.254362 0.656174 +v -0.417421 -0.254362 0.656174 +v -0.417421 -0.254362 0.656174 +v -0.163346 -0.249407 -0.003610 +v -0.163346 -0.249407 0.061781 +v -0.417421 -0.248290 0.649664 +v -0.448569 -0.247081 0.737319 +v -0.411461 -0.247081 0.737319 +v -0.156995 -0.245228 0.069521 +v -0.417421 -0.245183 0.717619 +v -0.448328 -0.242399 0.181010 +v -0.209010 -0.242399 0.181010 +v -0.209010 -0.242399 0.181010 +v -0.453264 -0.225458 0.446857 +v -0.453264 -0.225458 0.446857 +v -0.328552 -0.225458 0.446857 +v -0.328552 -0.225458 0.446857 +v -0.209010 -0.221641 -0.000076 +v 0.224384 -0.215578 -0.003610 +v 0.224384 -0.215578 0.061780 +v 0.231843 -0.214108 0.069521 +v -0.417421 -0.213995 0.684178 +v -0.409266 -0.202253 0.447416 +v -0.448569 -0.200853 0.608645 +v -0.411461 -0.200853 0.608645 +v -0.408631 -0.197887 0.343416 +v -0.236799 -0.195177 0.159738 +v -0.073372 -0.192034 0.042900 +v -0.144132 -0.191441 0.042900 +v 0.354864 -0.190810 0.048261 +v 0.284112 -0.189577 0.048261 +v -0.079641 -0.188616 0.048261 +v 0.001775 -0.188616 0.048261 +v 0.261248 -0.188616 0.048261 +v 0.117195 -0.188616 0.056181 +v -0.418841 -0.187839 0.155999 +v -0.418841 -0.185604 0.619447 +v -0.418841 -0.183765 0.405468 +v -0.337435 -0.176479 0.329128 +v -0.337435 -0.176479 0.329128 +v -0.448569 -0.173571 0.699646 +v -0.411461 -0.173571 0.699646 +v -0.337435 -0.173274 0.293854 +v -0.573651 -0.172251 0.629386 +v -0.447121 -0.172251 0.629386 +v -0.448569 -0.170962 0.670042 +v -0.411461 -0.170962 0.670042 +v -0.041661 -0.169327 0.069521 +v -0.456184 -0.167725 0.421160 +v -0.448569 -0.166506 0.772849 +v -0.411461 -0.166506 0.772849 +v -0.035310 -0.165147 -0.003610 +v -0.035310 -0.165147 0.061781 +v -0.456184 -0.164699 0.373593 +v -0.331314 -0.162781 0.326087 +v -0.337435 -0.158964 0.259908 +v -0.573651 -0.158077 -0.000154 +v -0.355117 -0.150433 0.373239 +v -0.320009 -0.150433 0.373239 +v -0.418841 -0.150307 0.407394 +v -0.137278 -0.149610 0.069521 +v -0.408631 -0.146232 0.350403 +v -0.418841 -0.144497 0.155600 +v -0.418841 -0.144312 0.623881 +v -0.349879 -0.143295 0.321760 +v -0.324990 -0.143295 0.321760 +v -0.324990 -0.143295 0.321760 +v -0.141458 -0.143260 -0.003610 +v -0.141458 -0.143260 0.061781 +v -0.469867 -0.141213 0.540531 +v -0.355117 -0.140534 0.395124 +v -0.320009 -0.140534 0.395124 +v -0.337435 -0.140354 0.244318 +v -0.448569 -0.140056 0.718761 +v -0.411461 -0.140056 0.718761 +v -0.353414 -0.139709 0.314261 +v -0.321455 -0.139709 0.314261 +v -0.321455 -0.139709 0.314261 +v -0.458937 -0.139254 0.534948 +v -0.353511 -0.138527 0.366454 +v -0.321615 -0.138527 0.366454 +v -0.409266 -0.138009 0.440060 +v -0.337563 -0.135585 0.406067 +v 0.286220 -0.133024 0.069521 +v -0.478152 -0.132373 0.540567 +v -0.353511 -0.131133 0.386968 +v -0.321615 -0.131133 0.386968 +v -0.337563 -0.127435 0.397225 +v 0.284750 -0.125565 -0.003610 +v 0.284750 -0.125565 0.061780 +v -0.394483 -0.125277 0.215549 +v -0.394483 -0.125277 0.215549 +v -0.274017 -0.124209 0.215549 +v -0.274017 -0.124209 0.215549 +v -0.459996 -0.122619 0.560762 +v -0.457890 -0.122242 0.559687 +v -0.448569 -0.121763 0.661191 +v -0.411461 -0.121763 0.661191 +v -0.475506 -0.121575 0.535020 +v -0.461784 -0.121221 0.560131 +v -0.325500 -0.121004 0.215549 +v -0.337563 -0.120799 0.345554 +v -0.337563 -0.120799 0.345554 +v -0.464576 -0.119615 0.529437 +v -0.461465 -0.119445 0.558424 +v -0.352729 -0.119152 0.356044 +v -0.352729 -0.119152 0.356044 +v -0.322398 -0.119152 0.356044 +v -0.322398 -0.119152 0.356044 +v -0.459359 -0.119068 0.557348 +v -0.346812 -0.118510 0.360137 +v -0.328314 -0.118510 0.360136 +v -0.330610 -0.116841 0.347841 +v -0.346812 -0.116500 0.372933 +v -0.328314 -0.116500 0.372933 +v -0.337435 -0.116048 0.234843 +v -0.352729 -0.115858 0.377025 +v -0.352729 -0.115858 0.377025 +v -0.322398 -0.115858 0.377025 +v -0.322398 -0.115858 0.377025 +v -0.337563 -0.115496 0.379331 +v -0.337563 -0.114210 0.387516 +v -0.337563 -0.114210 0.387516 +v 0.089122 -0.114174 0.217740 +v 0.356283 -0.109406 0.048261 +v 0.285531 -0.108173 0.048261 +v -0.361301 -0.100242 0.332992 +v -0.313826 -0.100242 0.332992 +v 0.163156 -0.099699 0.216267 +v -0.343686 -0.092311 0.377306 +v -0.332030 -0.092311 0.377306 +v -0.347241 -0.089578 0.401930 +v -0.328476 -0.089578 0.401930 +v 0.092303 -0.087916 0.215465 +v -0.337435 -0.085052 0.239781 +v -0.361301 -0.084424 0.370030 +v -0.313826 -0.084424 0.370030 +v -0.353414 -0.083704 0.292965 +v -0.321455 -0.083704 0.292965 +v -0.321455 -0.083704 0.292965 +v -0.326202 -0.082855 0.371622 +v -0.386786 -0.082836 0.199666 +v -0.281039 -0.082836 0.199666 +v 0.020675 -0.082593 0.214527 +v -0.325500 -0.082481 0.206902 +v 0.150239 -0.080368 0.174433 +v -0.330610 -0.076846 0.325114 +v -0.273006 -0.076783 0.206902 +v -0.273006 -0.076783 0.206902 +v 0.150239 -0.076588 0.214313 +v 0.150239 -0.076588 0.214313 +v -0.337563 -0.075774 0.317737 +v -0.337563 -0.075774 0.317737 +v -0.319093 -0.073770 0.394205 +v -0.324990 -0.073597 0.275141 +v -0.343686 -0.073398 0.365938 +v -0.332030 -0.073398 0.365938 +v -0.395157 -0.073222 0.206902 +v -0.395157 -0.073222 0.206902 +v -0.322398 -0.070233 0.326274 +v -0.322398 -0.070233 0.326274 +v -0.352729 -0.070233 0.326274 +v -0.352729 -0.070233 0.326274 +v -0.346812 -0.068071 0.329605 +v -0.328314 -0.068071 0.329605 +v 0.141871 -0.067865 0.267983 +v 0.038739 -0.063202 0.212951 +v 0.168646 -0.062247 0.267170 +v -0.346812 -0.061313 0.340018 +v -0.328314 -0.061313 0.340018 +v -0.343555 -0.059984 0.266330 +v -0.331314 -0.059984 0.266330 +v -0.331314 -0.059984 0.266330 +v -0.352729 -0.059151 0.343349 +v -0.352729 -0.059151 0.343349 +v -0.322398 -0.059151 0.343349 +v -0.322398 -0.059151 0.343349 +v -0.347241 -0.057963 0.386481 +v -0.328476 -0.057963 0.386481 +v -0.337563 -0.057934 0.345225 +v -0.337435 -0.055125 0.252007 +v -0.337435 -0.055125 0.252007 +v -0.322968 -0.054386 0.270191 +v -0.322968 -0.054386 0.270191 +v -0.337563 -0.053610 0.351886 +v -0.337563 -0.053610 0.351886 +v -0.322968 -0.052750 0.301897 +v -0.322968 -0.052750 0.301897 +v -0.321874 -0.051569 0.191459 +v -0.326348 -0.048713 0.277540 +v -0.072165 -0.047986 0.042900 +v 0.123871 -0.047969 0.273397 +v -0.142925 -0.047393 0.042900 +v -0.326325 -0.045416 0.231339 +v 0.214498 -0.044695 0.210672 +v -0.348386 -0.038814 0.305780 +v -0.326740 -0.038814 0.305780 +v 0.177422 -0.036733 0.271772 +v 0.190417 -0.033544 0.209934 +v -0.348386 -0.032710 0.319273 +v -0.326740 -0.032710 0.319273 +v -0.326325 -0.030656 0.230408 +v -0.337563 -0.029659 0.326020 +v 0.135999 -0.029571 0.151956 +v -0.434423 -0.026875 0.437641 +v 0.152199 -0.026172 0.151464 +v -0.445048 -0.025567 0.484215 +v -0.564585 -0.025189 0.484996 +v -0.326348 -0.025067 0.277540 +v -0.448282 -0.023882 0.507913 +v -0.561014 -0.023528 0.508837 +v -0.426600 -0.021946 0.394045 +v -0.426600 -0.021946 0.394045 +v -0.321874 -0.021273 0.189244 +v -0.454235 -0.020678 0.529610 +v -0.554436 -0.020371 0.530419 +v -0.322968 -0.019309 0.274988 +v -0.322968 -0.019309 0.274988 +v 0.125108 -0.017533 0.155232 +v -0.463093 -0.017464 0.548734 +v -0.545012 -0.017201 0.549451 +v -0.016041 -0.017018 0.207856 +v 0.159422 -0.016837 0.277186 +v -0.474635 -0.015181 0.564200 +v -0.533123 -0.014989 0.564771 +v -0.380872 -0.014709 0.191148 +v -0.487893 -0.014072 0.574195 +v -0.518949 -0.013936 0.574853 +v -0.503183 -0.013678 0.578140 +v -0.322968 -0.013537 0.288986 +v -0.322968 -0.013537 0.288986 +v 0.010006 -0.011885 0.207730 +v 0.157509 -0.010735 0.154248 +v -0.276384 -0.008655 0.191148 +v -0.346776 -0.006534 0.294361 +v -0.328351 -0.006534 0.294361 +v -0.438617 -0.006191 0.381588 +v -0.425094 -0.005993 0.384050 +v -0.425094 -0.005993 0.384050 +v -0.346776 -0.003103 0.306540 +v -0.328351 -0.003103 0.306540 +v -0.337563 -0.001388 0.312629 +v -0.448569 -0.000534 0.687263 +v -0.411461 -0.000534 0.687263 +v -0.176609 0.000000 0.920199 +v -0.448569 0.000381 0.754349 +v -0.411461 0.000381 0.754349 +v 0.146618 0.001303 0.157524 +v -0.573651 0.001530 0.713748 +v -0.447121 0.001530 0.713748 +v -0.502162 0.005192 0.577600 +v -0.486844 0.005319 0.573640 +v -0.517902 0.005407 0.574299 +v -0.473494 0.005911 0.563596 +v 0.358295 0.005996 0.058608 +v -0.531981 0.006112 0.564167 +v 0.287543 0.007230 0.058608 +v -0.461744 0.007451 0.548021 +v -0.543663 0.007734 0.548737 +v -0.452587 0.009780 0.528738 +v -0.552785 0.010139 0.529545 +v -0.427058 0.010334 0.393120 +v -0.427058 0.010334 0.393120 +v -0.446340 0.012005 0.506886 +v -0.559070 0.012401 0.507808 +v -0.442980 0.012645 0.483121 +v -0.562516 0.013054 0.483901 +v -0.435045 0.016967 0.436385 +v -0.349478 0.019621 0.285460 +v -0.325649 0.019621 0.285460 +v -0.349478 0.022175 0.301950 +v -0.325649 0.022175 0.301950 +v -0.337563 0.023452 0.310195 +v 0.197491 0.024774 0.204001 +v 0.223538 0.029827 0.203090 +v -0.281929 0.036991 0.190067 +v -0.386786 0.043401 0.190067 +v -0.322968 0.044584 0.244861 +v -0.322968 0.044584 0.244861 +v -0.322968 0.046407 0.268930 +v -0.322968 0.046407 0.268930 +v 0.017080 0.046433 0.201797 +v 0.017080 0.046433 0.201797 +v -0.321874 0.048453 0.190311 +v -0.326348 0.048498 0.251539 +v -0.347898 0.050474 0.281229 +v -0.327229 0.050474 0.281229 +v -0.337563 0.051528 0.267524 +v -0.337563 0.051528 0.267524 +v -0.347898 0.052690 0.295533 +v -0.327229 0.052690 0.295533 +v -0.352161 0.052928 0.277678 +v -0.352161 0.052928 0.277678 +v -0.322966 0.052928 0.277678 +v -0.322966 0.052928 0.277678 +v -0.337563 0.053798 0.302685 +v -0.352120 0.055725 0.297919 +v -0.352120 0.055725 0.297919 +v -0.323006 0.055725 0.297919 +v -0.323006 0.055725 0.297919 +v -0.337563 0.057120 0.307988 +v -0.337563 0.057120 0.307988 +v -0.007001 0.057504 0.200274 +v -0.007001 0.057504 0.200274 +v -0.326325 0.059011 0.224149 +v -0.071197 0.067430 0.057953 +v -0.141957 0.068023 0.057953 +v -0.326325 0.075773 0.223588 +v 0.168759 0.076091 0.198780 +v -0.321874 0.077050 0.194269 +v -0.326348 0.080353 0.249187 +v -0.272669 0.084176 0.206902 +v -0.272669 0.084176 0.206902 +v -0.393471 0.085244 0.206902 +v -0.393471 0.085244 0.206902 +v -0.322968 0.087109 0.262334 +v -0.322968 0.087109 0.262334 +v -0.322968 0.088392 0.247061 +v -0.322968 0.088392 0.247061 +v -0.331039 0.088806 0.206902 +v -0.386786 0.088875 0.199176 +v -0.281039 0.088875 0.199176 +v 0.057258 0.089477 0.197418 +v -0.349750 0.092754 0.271268 +v -0.325376 0.092754 0.271268 +v -0.325404 0.095138 0.288162 +v -0.349723 0.095138 0.288162 +v 0.186822 0.095402 0.196419 +v -0.337563 0.096328 0.296573 +v -0.121540 0.097689 -0.003610 +v -0.121540 0.097689 0.061781 +v 0.115195 0.100805 0.196266 +v 0.309342 0.100858 -0.003610 +v 0.309342 0.100858 0.061780 +v -0.119337 0.104966 0.069521 +v -0.462961 0.107048 0.552048 +v -0.465067 0.107425 0.553124 +v -0.468179 0.107595 0.524136 +v 0.309270 0.108460 0.069521 +v -0.465386 0.109201 0.554831 +v -0.479109 0.109555 0.529720 +v -0.461493 0.110222 0.554386 +v -0.463599 0.110599 0.555462 +v 0.044341 0.112508 0.194678 +v -0.333398 0.119893 0.213027 +v -0.481754 0.120353 0.535266 +v -0.448569 0.121505 0.652002 +v -0.411461 0.121505 0.652002 +v -0.397516 0.124879 0.213027 +v -0.397516 0.124879 0.213027 +v -0.409266 0.125617 0.440177 +v 0.118375 0.126983 0.193206 +v -0.274354 0.127016 0.213027 +v -0.274354 0.127016 0.213027 +v -0.462539 0.127234 0.529647 +v -0.473469 0.129193 0.535231 +v -0.337563 0.130993 0.243671 +v -0.337563 0.130993 0.243671 +v -0.357530 0.132978 0.257537 +v -0.357530 0.132978 0.257537 +v -0.317597 0.132978 0.257536 +v -0.317597 0.132978 0.257536 +v -0.420734 0.133152 0.158119 +v -0.408631 0.133840 0.350520 +v -0.337563 0.134184 0.252177 +v -0.448569 0.135465 0.729690 +v -0.411461 0.135465 0.729690 +v -0.351127 0.135638 0.261564 +v -0.323999 0.135638 0.261564 +v -0.317635 0.136945 0.285207 +v -0.317635 0.136945 0.285207 +v -0.357492 0.136945 0.285207 +v -0.357492 0.136945 0.285207 +v -0.351127 0.138546 0.280338 +v -0.323999 0.138546 0.280338 +v -0.337563 0.138924 0.298993 +v -0.337563 0.138924 0.298993 +v -0.420734 0.139397 0.623881 +v -0.420734 0.144015 0.400649 +v -0.059301 0.148140 0.048261 +v -0.025980 0.148824 -0.003610 +v -0.025980 0.148824 0.061781 +v -0.070515 0.148843 0.042900 +v -0.141275 0.149436 0.042900 +v 0.360806 0.150027 0.048261 +v 0.084736 0.150214 0.054316 +v -0.337563 0.150965 0.238514 +v -0.337563 0.150965 0.238514 +v -0.337563 0.150965 0.238514 +v -0.033257 0.151028 0.069521 +v 0.290054 0.151260 0.048261 +v -0.573651 0.151382 -0.000154 +v 0.200144 0.151877 0.048261 +v 0.281552 0.153049 0.048261 +v -0.357733 0.153127 0.252472 +v -0.357733 0.153127 0.252472 +v -0.357733 0.153127 0.252472 +v -0.317393 0.153127 0.252472 +v -0.317393 0.153127 0.252472 +v -0.317393 0.153127 0.252472 +v -0.357733 0.157451 0.280389 +v -0.357733 0.157451 0.280389 +v -0.357733 0.157451 0.280389 +v -0.317393 0.157451 0.280389 +v -0.317393 0.157451 0.280389 +v -0.317393 0.157451 0.280389 +v -0.456184 0.158984 0.385251 +v -0.337563 0.159613 0.294348 +v -0.337563 0.159613 0.294348 +v -0.456184 0.159770 0.416030 +v -0.337563 0.162246 0.244616 +v -0.351836 0.163776 0.254493 +v -0.323290 0.163776 0.254493 +v -0.573651 0.164474 0.619081 +v -0.447121 0.164474 0.619081 +v -0.351836 0.166836 0.274248 +v -0.323290 0.166836 0.274248 +v -0.448569 0.168495 0.670555 +v -0.411461 0.168495 0.670555 +v -0.448569 0.170319 0.699099 +v -0.411461 0.170319 0.699099 +v -0.420734 0.171165 0.398454 +v 0.231977 0.176759 -0.003610 +v 0.231977 0.176759 0.061780 +v -0.420734 0.176813 0.158518 +v 0.239579 0.176832 0.069521 +v 0.377641 0.178151 0.069521 +v 0.385244 0.178223 -0.003610 +v 0.385244 0.178223 0.061780 +v -0.420734 0.180689 0.619447 +v -0.448569 0.183746 0.775239 +v -0.411461 0.183746 0.775239 +v -0.408631 0.185495 0.343534 +v -0.236799 0.187208 0.160356 +v -0.409266 0.189861 0.447533 +v -0.448569 0.202230 0.614738 +v -0.411461 0.202230 0.614738 +v -0.209010 0.214239 -0.000076 +v -0.417421 0.216328 0.692649 +v -0.060320 0.218895 0.048261 +v 0.083717 0.220970 0.054316 +v 0.199125 0.222632 0.048261 +v 0.280533 0.223804 0.048261 +v -0.453264 0.231556 0.439620 +v -0.453264 0.231556 0.439620 +v -0.328552 0.231556 0.439620 +v -0.328552 0.231556 0.439620 +v -0.448328 0.234996 0.181010 +v -0.209010 0.234996 0.181010 +v -0.209010 0.234996 0.181010 +v -0.079319 0.237108 0.069521 +v -0.077115 0.244384 -0.003610 +v -0.077115 0.244384 0.061781 +v -0.417421 0.247516 0.726090 +v -0.369223 0.249701 0.539975 +v -0.434913 0.250185 0.331001 +v -0.356935 0.250185 0.392889 +v -0.427770 0.250185 0.396429 +v -0.448569 0.250444 0.747076 +v -0.411461 0.250444 0.747076 +v -0.360410 0.250475 0.102648 +v -0.417421 0.250623 0.658135 +v -0.447635 0.256695 0.664645 +v -0.417421 0.256695 0.664645 +v -0.417421 0.256695 0.664645 +v -0.388293 0.257418 0.602502 +v -0.384375 0.266441 0.538006 +v -0.370636 0.267443 0.545475 +v -0.403129 0.274433 0.585444 +v -0.350460 0.275970 0.503023 +v -0.394160 0.276745 0.592785 +v -0.434913 0.277542 0.331001 +v -0.356935 0.277542 0.392889 +v -0.427770 0.277542 0.396429 +v -0.373184 0.283125 0.503630 +v -0.361731 0.286772 0.514832 +v 0.184580 0.289839 0.008489 +v -0.340449 0.293949 0.473544 +v -0.446095 0.294556 0.102648 +v -0.429935 0.294774 0.652229 +v -0.411003 0.302276 0.649562 +v -0.411003 0.302276 0.649562 +v -0.358251 0.303948 0.477395 +v -0.344513 0.304950 0.484864 +v -0.418078 0.308704 0.626391 +v -0.418078 0.308704 0.626391 +v -0.404339 0.309706 0.633860 +v -0.404339 0.309706 0.633860 +v 0.133519 0.325597 0.055359 +v 0.133519 0.325597 0.055359 +v -0.412666 0.335410 0.652529 +v 0.129778 0.337160 0.151157 +v 0.129778 0.337160 0.151157 +v -0.337195 0.340276 0.445303 +v -0.416400 0.342731 0.626770 +v -0.358985 0.343152 0.458250 +v -0.402661 0.343732 0.634239 +v -0.345247 0.344154 0.465719 +v 0.115625 0.346246 0.054439 +v 0.115625 0.346246 0.131554 +v -0.176609 0.359250 0.848096 +v -0.453264 0.361479 0.434151 +v -0.328552 0.361479 0.434151 +v -0.409818 0.365747 0.641387 +v -0.409818 0.365747 0.641387 +v -0.411237 0.365796 0.663236 +v -0.411237 0.365796 0.663236 +v -0.424311 0.368196 0.639710 +v -0.424311 0.368196 0.639710 +v -0.437540 0.370240 0.660192 +v -0.370268 0.373002 0.542059 +v -0.176609 0.373210 0.900818 +v -0.176609 0.373210 0.900818 +v -0.374701 0.375703 0.553807 +v -0.266479 0.398144 0.055359 +v -0.266479 0.398144 0.055359 +v -0.351293 0.400130 0.470936 +v -0.337555 0.401132 0.478405 +v -0.260499 0.407943 0.151157 +v -0.260499 0.407943 0.151157 +v -0.249346 0.412439 0.054440 +v -0.249346 0.412439 0.131554 +v -0.329714 0.412834 0.460264 +v -0.409958 0.416769 0.618384 +v -0.396219 0.417771 0.625853 +v -0.401163 0.427555 0.639222 +v -0.434913 0.435082 0.331001 +v -0.356935 0.435082 0.392889 +v -0.427770 0.435082 0.396429 +v -0.361022 0.435885 0.499096 +v -0.347283 0.436886 0.506565 +v -0.395875 0.438057 0.585830 +v -0.382137 0.439059 0.593298 +v -0.151840 0.442825 0.176347 +v -0.151840 0.442825 0.176347 +v -0.242863 0.446929 0.784232 +v -0.376749 0.448804 0.544444 +v -0.342704 0.449744 0.496645 +v -0.363011 0.449805 0.551913 +v -0.383103 0.454857 0.597471 +v -0.239245 0.458678 0.193445 +v -0.239245 0.458678 0.193445 +v -0.434913 0.462431 0.331001 +v -0.356935 0.462431 0.392889 +v -0.427770 0.462431 0.396429 +v -0.361655 0.465788 0.550345 +v -0.244261 0.482028 0.825101 +v -0.244261 0.482028 0.825101 +v -0.453264 0.493882 0.439599 +v -0.453264 0.493882 0.439599 +v -0.328552 0.493882 0.439599 +v -0.328552 0.493882 0.439599 +v -0.292662 0.504722 0.452463 +v -0.292662 0.504722 0.543283 +v -0.301727 0.514091 0.429362 +v -0.301727 0.514091 0.566384 +v -0.389896 0.527909 0.140847 +v -0.458150 0.531147 0.366718 +v -0.315313 0.531147 0.366718 +v -0.443810 0.533656 0.140847 +v -0.335982 0.533656 0.140847 +v -0.290416 0.544425 0.729348 +v -0.490319 0.564554 0.140847 +v -0.289472 0.564554 0.140847 +v 0.177965 0.566483 0.151157 +v 0.177965 0.566483 0.151157 +v 0.118020 0.567224 0.074088 +v -0.125064 0.570251 0.176347 +v -0.125064 0.570251 0.176347 +v -0.334654 0.573301 0.608309 +v 0.186922 0.579744 0.055359 +v 0.186922 0.579744 0.055359 +v -0.315313 0.581021 0.366718 +v -0.321318 0.581021 0.497917 +v -0.212470 0.586103 0.193445 +v -0.212470 0.586103 0.193445 +v -0.224316 0.589008 0.348718 +v -0.178647 0.590517 0.303093 +v -0.179534 0.594046 0.298478 +v -0.030698 0.594196 0.074088 +v -0.172703 0.596556 0.307707 +v -0.177007 0.598830 0.298478 +v -0.173591 0.600085 0.303093 +v -0.234249 0.610135 0.330738 +v -0.188148 0.617535 0.366699 +v -0.273208 0.625179 0.140847 +v -0.204283 0.626893 0.054440 +v -0.204283 0.626893 0.131554 +v -0.221131 0.634962 0.330738 +v 0.121516 0.635767 0.058063 +v -0.212311 0.637266 0.151157 +v -0.212311 0.637266 0.151157 +v 0.111844 0.637521 -0.003114 +v -0.198080 0.638663 0.348718 +v 0.134023 0.643383 0.076372 +v 0.134023 0.643383 0.076372 +v -0.213076 0.652290 0.055359 +v -0.213076 0.652290 0.055359 +v 0.003482 0.657175 -0.003114 +v -0.006190 0.658929 0.058063 +v -0.014695 0.670356 0.076372 +v -0.014695 0.670356 0.076372 +v -0.328648 0.699983 0.338447 +v -0.318654 0.700484 0.140847 +v -0.294027 0.701718 0.921101 +v -0.489845 0.712765 0.140744 +v -0.289947 0.712765 0.140847 +v -0.289947 0.712765 0.140847 +v -0.575491 0.739535 0.001923 +v -0.575491 0.739535 0.001923 +v -0.114645 0.739535 0.001923 +v 0.319177 0.739535 0.001923 +v 0.741905 0.739535 0.001923 +v 0.741905 0.739535 0.001923 +vt 0.198708 0.637534 +vt 0.234555 0.810452 +vt 0.237885 0.660782 +vt 0.156512 0.657538 +vt 0.157310 0.809737 +vt 0.032586 0.305514 +vt 0.138994 0.214943 +vt 0.032703 0.214805 +vt 0.344498 0.305514 +vt 0.344615 0.215210 +vt 0.226915 0.215057 +vt 0.344863 0.024180 +vt 0.227035 0.122413 +vt 0.344735 0.122566 +vt 0.555366 0.620808 +vt 0.525425 0.621990 +vt 0.545979 0.640150 +vt 0.555366 0.620808 +vt 0.545979 0.640150 +vt 0.525425 0.621990 +vt 0.703497 0.632040 +vt 0.703766 0.644967 +vt 0.709725 0.620711 +vt 0.751722 0.631037 +vt 0.745762 0.655292 +vt 0.751990 0.643963 +vt 0.408401 0.317210 +vt 0.426405 0.315345 +vt 0.390399 0.315345 +vt 0.408401 0.317210 +vt 0.426405 0.315345 +vt 0.390399 0.315345 +vt 0.408393 0.317216 +vt 0.426397 0.315351 +vt 0.390392 0.315351 +vt 0.627038 0.072824 +vt 0.629668 0.078340 +vt 0.650736 0.078532 +vt 0.675500 0.071756 +vt 0.672445 0.078298 +vt 0.600993 0.074672 +vt 0.579813 0.068877 +vt 0.582426 0.074473 +vt 0.621262 0.069791 +vt 0.619012 0.074508 +vt 0.627038 0.072824 +vt 0.629668 0.078340 +vt 0.650736 0.078532 +vt 0.675500 0.071756 +vt 0.672445 0.078298 +vt 0.600993 0.074672 +vt 0.579813 0.068877 +vt 0.582426 0.074473 +vt 0.621262 0.069791 +vt 0.619012 0.074508 +vt 0.627038 0.072824 +vt 0.650736 0.078532 +vt 0.629668 0.078340 +vt 0.675500 0.071756 +vt 0.672445 0.078298 +vt 0.600993 0.074672 +vt 0.582426 0.074473 +vt 0.579813 0.068877 +vt 0.621262 0.069791 +vt 0.619012 0.074508 +vt 0.220644 0.128904 +vt 0.207065 0.139746 +vt 0.220956 0.139618 +vt 0.220638 0.128912 +vt 0.220949 0.139623 +vt 0.207062 0.139750 +vt 0.220644 0.128904 +vt 0.207065 0.139746 +vt 0.220956 0.139618 +vt 0.220638 0.128912 +vt 0.220949 0.139623 +vt 0.207062 0.139750 +vt 0.220644 0.128904 +vt 0.207065 0.139746 +vt 0.220956 0.139618 +vt 0.220644 0.128904 +vt 0.220956 0.139618 +vt 0.207065 0.139746 +vt 0.220644 0.128904 +vt 0.207065 0.139746 +vt 0.220956 0.139618 +vt 0.220644 0.128904 +vt 0.220956 0.139618 +vt 0.207065 0.139746 +vt 0.949122 0.597231 +vt 0.955406 0.598935 +vt 0.961638 0.596347 +vt 0.961430 0.578168 +vt 0.955147 0.576465 +vt 0.948914 0.579054 +vt 0.949123 0.597232 +vt 0.955406 0.598935 +vt 0.961638 0.596348 +vt 0.961430 0.578168 +vt 0.955147 0.576465 +vt 0.948914 0.579054 +vt 0.860312 0.451857 +vt 0.874741 0.453387 +vt 0.868700 0.443409 +vt 0.537019 0.284964 +vt 0.536350 0.273283 +vt 0.546464 0.281809 +vt 0.537019 0.284964 +vt 0.536350 0.273283 +vt 0.546464 0.281809 +vt 0.537019 0.284964 +vt 0.546464 0.281809 +vt 0.536350 0.273283 +vt 0.204156 0.857615 +vt 0.271518 0.856214 +vt 0.271518 0.901396 +vt 0.204156 0.899995 +vt 0.010645 0.858310 +vt 0.204156 0.857615 +vt 0.204156 0.899995 +vt 0.010645 0.899297 +vt 0.204156 0.857615 +vt 0.010645 0.858310 +vt 0.010645 0.899297 +vt 0.204156 0.899995 +vt 0.008767 0.853584 +vt 0.152514 0.854868 +vt 0.147622 0.650113 +vt 0.012735 0.644234 +vt 0.147622 0.650113 +vt 0.152514 0.854868 +vt 0.008767 0.853584 +vt 0.012735 0.644234 +vt 0.059247 0.588517 +vt 0.087227 0.575408 +vt 0.204111 0.622597 +vt 0.204330 0.573554 +vt 0.087227 0.575408 +vt 0.009995 0.569599 +vt 0.316824 0.570065 +vt 0.316985 0.334568 +vt 0.010155 0.334103 +vt 0.566967 0.211539 +vt 0.566967 0.239347 +vt 0.629407 0.242384 +vt 0.629108 0.214280 +vt 0.705424 0.239521 +vt 0.705742 0.213331 +vt 0.773908 0.241927 +vt 0.774203 0.215096 +vt 0.822129 0.240247 +vt 0.822129 0.214523 +vt 0.514444 0.402537 +vt 0.514444 0.415372 +vt 0.526844 0.415372 +vt 0.526844 0.402537 +vt 0.551999 0.402537 +vt 0.551999 0.415372 +vt 0.564669 0.415372 +vt 0.564669 0.402537 +vt 0.481995 0.432843 +vt 0.471706 0.449305 +vt 0.481995 0.454792 +vt 0.492285 0.449305 +vt 0.492285 0.438331 +vt 0.627840 0.440285 +vt 0.649325 0.454437 +vt 0.638135 0.450043 +vt 0.627840 0.440285 +vt 0.638135 0.450043 +vt 0.649325 0.454437 +vt 0.587934 0.407259 +vt 0.582871 0.420766 +vt 0.588185 0.439122 +vt 0.595805 0.411663 +vt 0.582871 0.420766 +vt 0.576238 0.424739 +vt 0.583448 0.435461 +vt 0.588185 0.439122 +vt 0.645320 0.565656 +vt 0.663068 0.565657 +vt 0.663068 0.557037 +vt 0.628080 0.565656 +vt 0.645320 0.565656 +vt 0.645320 0.557037 +vt 0.611505 0.565656 +vt 0.628080 0.565656 +vt 0.628080 0.557037 +vt 0.611503 0.557037 +vt 0.595075 0.565662 +vt 0.611505 0.565656 +vt 0.611503 0.557037 +vt 0.595073 0.557045 +vt 0.579433 0.565670 +vt 0.595075 0.565662 +vt 0.595073 0.557045 +vt 0.579431 0.557052 +vt 0.663068 0.565657 +vt 0.682203 0.565656 +vt 0.682203 0.557036 +vt 0.663068 0.557037 +vt 0.680069 0.478679 +vt 0.687342 0.468265 +vt 0.658207 0.468260 +vt 0.665470 0.478685 +vt 0.665506 0.457893 +vt 0.680029 0.457886 +vt 0.616080 0.372041 +vt 0.619967 0.373248 +vt 0.622910 0.364541 +vt 0.619586 0.360479 +vt 0.576422 0.367054 +vt 0.572607 0.369039 +vt 0.574931 0.380349 +vt 0.579220 0.380289 +vt 0.618617 0.383510 +vt 0.573607 0.390062 +vt 0.578360 0.393861 +vt 0.614474 0.383641 +vt 0.615443 0.396067 +vt 0.620348 0.393075 +vt 0.619586 0.360479 +vt 0.619967 0.373248 +vt 0.616080 0.372041 +vt 0.573305 0.354868 +vt 0.576423 0.367054 +vt 0.572607 0.369039 +vt 0.569637 0.359136 +vt 0.618617 0.383510 +vt 0.614475 0.383641 +vt 0.579220 0.380289 +vt 0.574931 0.380349 +vt 0.620348 0.393075 +vt 0.615443 0.396067 +vt 0.578360 0.393861 +vt 0.573607 0.390062 +vt 0.573306 0.354868 +vt 0.569637 0.359136 +vt 0.595079 0.577424 +vt 0.611505 0.565656 +vt 0.595075 0.565662 +vt 0.611507 0.577419 +vt 0.628080 0.577419 +vt 0.628080 0.565656 +vt 0.611505 0.565656 +vt 0.628080 0.577419 +vt 0.645320 0.577419 +vt 0.645320 0.565656 +vt 0.628080 0.565656 +vt 0.645320 0.577419 +vt 0.663068 0.577419 +vt 0.663068 0.565657 +vt 0.645320 0.565656 +vt 0.663068 0.577419 +vt 0.682203 0.565656 +vt 0.663068 0.565657 +vt 0.579439 0.577431 +vt 0.595079 0.577424 +vt 0.595075 0.565662 +vt 0.475770 0.455325 +vt 0.466987 0.462784 +vt 0.468340 0.475726 +vt 0.478477 0.481208 +vt 0.347338 0.540059 +vt 0.364235 0.537567 +vt 0.361352 0.520540 +vt 0.342683 0.476976 +vt 0.361217 0.475862 +vt 0.362972 0.459025 +vt 0.344748 0.457135 +vt 0.364235 0.537567 +vt 0.347338 0.540059 +vt 0.344424 0.520271 +vt 0.361352 0.520540 +vt 0.361217 0.475862 +vt 0.344748 0.457135 +vt 0.362972 0.459025 +vt 0.220222 0.595179 +vt 0.232442 0.591968 +vt 0.233884 0.586760 +vt 0.219438 0.583879 +vt 0.233884 0.586760 +vt 0.235697 0.577649 +vt 0.221250 0.574774 +vt 0.222594 0.604159 +vt 0.234799 0.600948 +vt 0.232442 0.591968 +vt 0.220222 0.595179 +vt 0.210959 0.595820 +vt 0.220222 0.595179 +vt 0.219438 0.583879 +vt 0.210177 0.584519 +vt 0.308714 0.586081 +vt 0.310371 0.601710 +vt 0.315830 0.600895 +vt 0.317127 0.586207 +vt 0.317127 0.586207 +vt 0.315830 0.600895 +vt 0.325129 0.601731 +vt 0.326425 0.587016 +vt 0.299412 0.587062 +vt 0.301074 0.602722 +vt 0.310371 0.601710 +vt 0.308714 0.586081 +vt 0.310371 0.601710 +vt 0.311744 0.611057 +vt 0.317252 0.610242 +vt 0.315830 0.600895 +vt 0.308859 0.577039 +vt 0.317127 0.586207 +vt 0.317253 0.577163 +vt 0.358855 0.609095 +vt 0.358383 0.584027 +vt 0.338193 0.584865 +vt 0.338663 0.609932 +vt 0.414243 0.614163 +vt 0.418771 0.594355 +vt 0.399013 0.589480 +vt 0.394498 0.609284 +vt 0.748398 0.675457 +vt 0.748784 0.691477 +vt 0.659783 0.688498 +vt 0.660428 0.678715 +vt 0.578565 0.691342 +vt 0.579945 0.676595 +vt 0.748398 0.675457 +vt 0.660428 0.678715 +vt 0.659783 0.688498 +vt 0.579945 0.676595 +vt 0.578565 0.691342 +vt 0.735680 0.392351 +vt 0.834453 0.392351 +vt 0.835030 0.382989 +vt 0.735271 0.377828 +vt 0.735271 0.377828 +vt 0.835030 0.382989 +vt 0.836912 0.346779 +vt 0.737131 0.341597 +vt 0.735672 0.428636 +vt 0.834459 0.428636 +vt 0.834453 0.392351 +vt 0.735680 0.392351 +vt 0.358854 0.609097 +vt 0.338661 0.609932 +vt 0.338191 0.584864 +vt 0.358382 0.584027 +vt 0.414245 0.614164 +vt 0.394499 0.609285 +vt 0.399014 0.589479 +vt 0.418774 0.594355 +vt 0.139115 0.122299 +vt 0.032824 0.122161 +vt 0.367259 0.122595 +vt 0.367259 0.024180 +vt 0.344863 0.024180 +vt 0.344735 0.122566 +vt 0.367259 0.215239 +vt 0.344615 0.215210 +vt 0.367259 0.305514 +vt 0.344498 0.305514 +vt 0.010555 0.024180 +vt 0.010428 0.122132 +vt 0.032824 0.122161 +vt 0.032951 0.024180 +vt 0.010307 0.214776 +vt 0.032703 0.214805 +vt 0.032586 0.305514 +vt 0.032951 0.024180 +vt 0.032980 0.001349 +vt 0.032951 0.024180 +vt 0.344863 0.024180 +vt 0.344892 0.001754 +vt 0.032586 0.305514 +vt 0.032557 0.327476 +vt 0.344469 0.327881 +vt 0.344498 0.305514 +vt 0.362282 0.405026 +vt 0.362002 0.420574 +vt 0.366540 0.420627 +vt 0.366819 0.405137 +vt 0.362002 0.420574 +vt 0.330516 0.419972 +vt 0.344622 0.438430 +vt 0.364777 0.424145 +vt 0.373688 0.436665 +vt 0.364777 0.424145 +vt 0.344622 0.438430 +vt 0.353504 0.450978 +vt 0.330767 0.404645 +vt 0.330516 0.419972 +vt 0.362002 0.420574 +vt 0.362282 0.405026 +vt 0.362282 0.405026 +vt 0.366819 0.405137 +vt 0.366541 0.420627 +vt 0.362002 0.420574 +vt 0.362002 0.420574 +vt 0.364778 0.424144 +vt 0.344622 0.438430 +vt 0.330516 0.419972 +vt 0.373688 0.436665 +vt 0.353504 0.450978 +vt 0.344622 0.438430 +vt 0.364778 0.424144 +vt 0.330767 0.404645 +vt 0.362282 0.405026 +vt 0.362002 0.420574 +vt 0.330516 0.419972 +vt 0.579622 0.625485 +vt 0.557362 0.640988 +vt 0.564761 0.662583 +vt 0.580476 0.661685 +vt 0.535133 0.601239 +vt 0.526845 0.603327 +vt 0.515615 0.633980 +vt 0.533779 0.656167 +vt 0.579622 0.625485 +vt 0.580476 0.661685 +vt 0.564761 0.662583 +vt 0.557362 0.640988 +vt 0.526845 0.603327 +vt 0.533779 0.656167 +vt 0.515615 0.633980 +vt 0.523988 0.718247 +vt 0.550740 0.718669 +vt 0.576483 0.581689 +vt 0.554962 0.581699 +vt 0.554955 0.597452 +vt 0.576476 0.597484 +vt 0.588662 0.581696 +vt 0.576483 0.581689 +vt 0.576476 0.597484 +vt 0.588654 0.597490 +vt 0.678338 0.581744 +vt 0.669282 0.581740 +vt 0.669273 0.597537 +vt 0.678328 0.597542 +vt 0.636647 0.581726 +vt 0.608401 0.581707 +vt 0.608393 0.597502 +vt 0.636638 0.597515 +vt 0.608401 0.581707 +vt 0.588662 0.581696 +vt 0.588654 0.597490 +vt 0.608393 0.597502 +vt 0.554962 0.581699 +vt 0.487466 0.581644 +vt 0.487458 0.597440 +vt 0.554955 0.597452 +vt 0.554962 0.581699 +vt 0.576483 0.581689 +vt 0.576476 0.597484 +vt 0.554955 0.597452 +vt 0.669282 0.581740 +vt 0.678338 0.581744 +vt 0.678328 0.597542 +vt 0.669273 0.597537 +vt 0.588662 0.581696 +vt 0.608401 0.581707 +vt 0.608393 0.597502 +vt 0.588654 0.597490 +vt 0.608401 0.581707 +vt 0.636647 0.581726 +vt 0.636638 0.597515 +vt 0.608393 0.597502 +vt 0.769639 0.651693 +vt 0.776566 0.655127 +vt 0.776876 0.635825 +vt 0.767555 0.635563 +vt 0.786196 0.636039 +vt 0.769050 0.656354 +vt 0.776390 0.660423 +vt 0.776566 0.655127 +vt 0.769639 0.651693 +vt 0.776390 0.660423 +vt 0.783467 0.656971 +vt 0.783360 0.652036 +vt 0.776566 0.655127 +vt 0.783360 0.652036 +vt 0.789129 0.652754 +vt 0.791961 0.636729 +vt 0.786196 0.636039 +vt 0.763891 0.652210 +vt 0.769639 0.651693 +vt 0.767555 0.635563 +vt 0.761781 0.636010 +vt 0.721785 0.662257 +vt 0.745028 0.619977 +vt 0.733703 0.613746 +vt 0.720782 0.614015 +vt 0.710460 0.656026 +vt 0.734706 0.661988 +vt 0.600539 0.939346 +vt 0.600318 0.907033 +vt 0.611602 0.906953 +vt 0.611815 0.939264 +vt 0.600539 0.939346 +vt 0.611815 0.939264 +vt 0.611602 0.906953 +vt 0.600318 0.907033 +vt 0.918026 0.263616 +vt 0.931187 0.286410 +vt 0.957508 0.286196 +vt 0.970667 0.263616 +vt 0.957508 0.240822 +vt 0.931187 0.240822 +vt 0.239664 0.713314 +vt 0.258710 0.746302 +vt 0.296801 0.745992 +vt 0.315846 0.713314 +vt 0.296801 0.680327 +vt 0.258710 0.680327 +vt 0.621440 0.720752 +vt 0.616770 0.720752 +vt 0.616770 0.727266 +vt 0.621440 0.727266 +vt 0.982500 0.722041 +vt 0.989364 0.722142 +vt 0.988048 0.694953 +vt 0.981184 0.694852 +vt 0.995131 0.718586 +vt 0.994168 0.698683 +vt 0.975416 0.698408 +vt 0.976380 0.718311 +vt 0.972290 0.704667 +vt 0.998258 0.712327 +vt 0.997905 0.705042 +vt 0.632629 0.660273 +vt 0.632629 0.652215 +vt 0.585126 0.652215 +vt 0.632629 0.652215 +vt 0.632629 0.644505 +vt 0.585126 0.644505 +vt 0.585126 0.652215 +vt 0.632629 0.644505 +vt 0.632629 0.636445 +vt 0.585126 0.636445 +vt 0.585126 0.644505 +vt 0.632629 0.628080 +vt 0.632629 0.620657 +vt 0.585126 0.620657 +vt 0.585126 0.628079 +vt 0.632629 0.620657 +vt 0.632629 0.612305 +vt 0.585126 0.612305 +vt 0.585126 0.620657 +vt 0.664117 0.425566 +vt 0.656733 0.437835 +vt 0.664181 0.450016 +vt 0.679015 0.449938 +vt 0.686402 0.437669 +vt 0.678953 0.425479 +vt 0.862652 0.294818 +vt 0.900958 0.295147 +vt 0.901080 0.287611 +vt 0.862738 0.287145 +vt 0.862738 0.287145 +vt 0.900850 0.261062 +vt 0.862513 0.260394 +vt 0.862513 0.260394 +vt 0.900850 0.261062 +vt 0.901105 0.248424 +vt 0.862745 0.247734 +vt 0.908542 0.288975 +vt 0.913220 0.263350 +vt 0.900850 0.261062 +vt 0.901080 0.287611 +vt 0.850116 0.262865 +vt 0.855231 0.288633 +vt 0.862738 0.287145 +vt 0.862513 0.260394 +vt 0.968933 0.438529 +vt 0.968933 0.541315 +vt 0.990302 0.541315 +vt 0.990302 0.438529 +vt 0.990302 0.377804 +vt 0.990301 0.275017 +vt 0.968932 0.275017 +vt 0.968933 0.377804 +vt 0.968933 0.377804 +vt 0.968933 0.438529 +vt 0.990302 0.438529 +vt 0.990302 0.377804 +vt 0.838109 0.242946 +vt 0.882559 0.242946 +vt 0.882627 0.220025 +vt 0.838109 0.220025 +vt 0.838109 0.220025 +vt 0.882627 0.220025 +vt 0.882559 0.214516 +vt 0.838109 0.214516 +vt 0.761114 0.125281 +vt 0.793028 0.125281 +vt 0.793028 0.108849 +vt 0.761162 0.108849 +vt 0.888136 0.242947 +vt 0.888136 0.220025 +vt 0.882627 0.220025 +vt 0.882559 0.242946 +vt 0.755083 0.109138 +vt 0.731835 0.108988 +vt 0.729677 0.121890 +vt 0.757076 0.122066 +vt 0.919081 0.230139 +vt 0.919296 0.175990 +vt 0.893660 0.179994 +vt 0.893562 0.225961 +vt 0.891819 0.169066 +vt 0.893660 0.179994 +vt 0.919296 0.175990 +vt 0.917552 0.164869 +vt 0.893562 0.225961 +vt 0.891845 0.236798 +vt 0.917357 0.240962 +vt 0.919081 0.230139 +vt 0.743574 0.104480 +vt 0.850272 0.102825 +vt 0.850272 0.088665 +vt 0.743574 0.088665 +vt 0.743574 0.088665 +vt 0.850272 0.088665 +vt 0.850272 0.020501 +vt 0.743574 0.020501 +vt 0.743574 0.020501 +vt 0.850272 0.020501 +vt 0.850272 0.003030 +vt 0.865709 0.090825 +vt 0.865709 0.015405 +vt 0.850272 0.020501 +vt 0.850272 0.088665 +vt 0.728224 0.016685 +vt 0.727304 0.092105 +vt 0.743574 0.088665 +vt 0.743574 0.020501 +vt 0.983460 0.080071 +vt 0.883113 0.080071 +vt 0.883113 0.088298 +vt 0.983460 0.088298 +vt 0.883113 0.088298 +vt 0.883113 0.147522 +vt 0.983460 0.147522 +vt 0.983460 0.147522 +vt 0.883113 0.147522 +vt 0.883113 0.154776 +vt 0.983460 0.155066 +vt 0.873752 0.076169 +vt 0.992940 0.076169 +vt 0.992940 0.005826 +vt 0.873752 0.005826 +vt 0.991462 0.147522 +vt 0.991462 0.088298 +vt 0.983460 0.088298 +vt 0.983460 0.147522 +vt 0.952457 0.996821 +vt 0.970560 0.997140 +vt 0.970981 0.973276 +vt 0.955902 0.974128 +vt 0.624658 0.959808 +vt 0.613835 0.988165 +vt 0.623014 0.991721 +vt 0.633890 0.963368 +vt 0.639268 0.971669 +vt 0.632533 0.989195 +vt 0.615092 0.962349 +vt 0.608834 0.970042 +vt 0.608284 0.979956 +vt 0.223918 0.910653 +vt 0.233000 0.910653 +vt 0.233000 0.907305 +vt 0.223918 0.907305 +vt 0.242220 0.910653 +vt 0.242220 0.907305 +vt 0.251161 0.910653 +vt 0.251160 0.907305 +vt 0.260035 0.910653 +vt 0.260035 0.907305 +vt 0.268544 0.910653 +vt 0.268544 0.907305 +vt 0.277163 0.910653 +vt 0.277163 0.907305 +vt 0.207906 0.910653 +vt 0.215683 0.910653 +vt 0.215683 0.907305 +vt 0.207906 0.907305 +vt 0.853675 0.858557 +vt 0.855302 0.845863 +vt 0.843172 0.844352 +vt 0.841518 0.857027 +vt 0.649959 0.965912 +vt 0.696430 0.965912 +vt 0.696430 0.952047 +vt 0.649959 0.952047 +vt 0.696430 0.972673 +vt 0.649959 0.972673 +vt 0.649959 0.984788 +vt 0.696430 0.984788 +vt 0.649959 0.984788 +vt 0.649959 0.991922 +vt 0.696430 0.991922 +vt 0.696430 0.984788 +vt 0.696430 0.972673 +vt 0.696430 0.965912 +vt 0.649959 0.965912 +vt 0.649959 0.972673 +vt 0.958798 0.528001 +vt 0.958798 0.513416 +vt 0.892225 0.513416 +vt 0.892225 0.528002 +vt 0.892225 0.495377 +vt 0.892225 0.507565 +vt 0.958798 0.507565 +vt 0.958798 0.495376 +vt 0.958798 0.507565 +vt 0.892225 0.507565 +vt 0.892225 0.513416 +vt 0.958798 0.513416 +vt 0.892225 0.495377 +vt 0.958798 0.495376 +vt 0.958798 0.490659 +vt 0.892225 0.490659 +vt 0.650085 0.965852 +vt 0.696430 0.965852 +vt 0.696430 0.952047 +vt 0.650085 0.952047 +vt 0.696430 0.972605 +vt 0.650085 0.972605 +vt 0.650085 0.984705 +vt 0.696430 0.984705 +vt 0.650085 0.984705 +vt 0.650085 0.991922 +vt 0.696430 0.991922 +vt 0.696430 0.984705 +vt 0.696430 0.972605 +vt 0.696430 0.965852 +vt 0.650085 0.965852 +vt 0.650085 0.972605 +vt 0.958798 0.528001 +vt 0.958798 0.513415 +vt 0.892225 0.513415 +vt 0.892225 0.528001 +vt 0.892225 0.495377 +vt 0.892225 0.507565 +vt 0.958798 0.507565 +vt 0.958798 0.495376 +vt 0.958798 0.507565 +vt 0.892225 0.507565 +vt 0.892225 0.513415 +vt 0.958798 0.513415 +vt 0.892225 0.495377 +vt 0.958798 0.495376 +vt 0.958798 0.490658 +vt 0.892225 0.490658 +vt 0.592711 0.965417 +vt 0.588222 0.951603 +vt 0.550219 0.951608 +vt 0.545739 0.965419 +vt 0.550215 0.979216 +vt 0.588215 0.979224 +vt 0.576476 0.987751 +vt 0.561961 0.987757 +vt 0.576475 0.943077 +vt 0.561967 0.943074 +vt 0.797692 0.676526 +vt 0.795813 0.667596 +vt 0.772302 0.670825 +vt 0.773357 0.675835 +vt 0.795813 0.667596 +vt 0.792431 0.659122 +vt 0.770406 0.666076 +vt 0.772302 0.670825 +vt 0.791452 0.693604 +vt 0.795301 0.685331 +vt 0.772017 0.680774 +vt 0.769856 0.685414 +vt 0.795301 0.685331 +vt 0.773357 0.675835 +vt 0.772017 0.680774 +vt 0.797702 0.676526 +vt 0.795820 0.667591 +vt 0.772294 0.670822 +vt 0.773350 0.675834 +vt 0.795820 0.667591 +vt 0.792437 0.659110 +vt 0.770398 0.666069 +vt 0.791458 0.693615 +vt 0.795308 0.685336 +vt 0.772010 0.680776 +vt 0.769848 0.685420 +vt 0.795308 0.685336 +vt 0.797702 0.676526 +vt 0.773350 0.675834 +vt 0.772010 0.680776 +vt 0.797702 0.676526 +vt 0.795820 0.667591 +vt 0.772294 0.670822 +vt 0.773350 0.675834 +vt 0.795820 0.667591 +vt 0.792437 0.659111 +vt 0.770398 0.666069 +vt 0.791457 0.693615 +vt 0.795308 0.685336 +vt 0.772010 0.680776 +vt 0.769848 0.685420 +vt 0.795308 0.685336 +vt 0.797702 0.676526 +vt 0.773350 0.675834 +vt 0.772010 0.680776 +vt 0.797692 0.676526 +vt 0.795813 0.667596 +vt 0.772302 0.670825 +vt 0.773357 0.675835 +vt 0.795813 0.667596 +vt 0.792431 0.659122 +vt 0.770406 0.666076 +vt 0.772302 0.670825 +vt 0.791452 0.693604 +vt 0.772017 0.680774 +vt 0.769856 0.685414 +vt 0.795301 0.685331 +vt 0.797692 0.676526 +vt 0.773357 0.675835 +vt 0.772017 0.680774 +vt 0.797692 0.676526 +vt 0.795813 0.667596 +vt 0.772302 0.670825 +vt 0.773357 0.675835 +vt 0.795813 0.667596 +vt 0.792431 0.659122 +vt 0.770406 0.666076 +vt 0.772302 0.670825 +vt 0.791452 0.693604 +vt 0.795301 0.685331 +vt 0.772017 0.680774 +vt 0.769856 0.685414 +vt 0.795301 0.685331 +vt 0.797692 0.676526 +vt 0.773357 0.675835 +vt 0.592711 0.965417 +vt 0.588222 0.951603 +vt 0.550219 0.951608 +vt 0.545739 0.965419 +vt 0.550215 0.979216 +vt 0.588215 0.979224 +vt 0.576476 0.987751 +vt 0.561961 0.987757 +vt 0.576475 0.943077 +vt 0.561967 0.943074 +vt 0.797692 0.676526 +vt 0.795813 0.667596 +vt 0.772302 0.670825 +vt 0.773357 0.675835 +vt 0.795813 0.667596 +vt 0.792431 0.659122 +vt 0.770406 0.666076 +vt 0.772302 0.670825 +vt 0.791452 0.693604 +vt 0.772017 0.680774 +vt 0.769856 0.685414 +vt 0.795301 0.685331 +vt 0.797692 0.676526 +vt 0.773357 0.675835 +vt 0.772017 0.680774 +vt 0.797692 0.676526 +vt 0.795813 0.667596 +vt 0.772302 0.670825 +vt 0.773357 0.675835 +vt 0.795813 0.667596 +vt 0.770406 0.666076 +vt 0.772302 0.670825 +vt 0.791452 0.693604 +vt 0.795301 0.685331 +vt 0.772017 0.680774 +vt 0.769856 0.685414 +vt 0.795301 0.685331 +vt 0.797692 0.676526 +vt 0.773357 0.675835 +vt 0.772017 0.680774 +vt 0.797692 0.676526 +vt 0.795813 0.667596 +vt 0.772302 0.670825 +vt 0.773357 0.675835 +vt 0.795813 0.667596 +vt 0.792431 0.659122 +vt 0.770406 0.666076 +vt 0.772302 0.670825 +vt 0.791452 0.693604 +vt 0.795301 0.685331 +vt 0.772017 0.680774 +vt 0.769856 0.685414 +vt 0.795301 0.685331 +vt 0.797692 0.676526 +vt 0.773357 0.675835 +vt 0.772017 0.680774 +vt 0.743085 0.844945 +vt 0.745467 0.860837 +vt 0.760808 0.858542 +vt 0.758528 0.842570 +vt 0.853675 0.858558 +vt 0.855302 0.845863 +vt 0.843172 0.844352 +vt 0.841518 0.857027 +vt 0.452325 0.997102 +vt 0.468568 0.991563 +vt 0.467918 0.946644 +vt 0.451523 0.941579 +vt 0.435280 0.947116 +vt 0.435936 0.992037 +vt 0.478205 0.960378 +vt 0.478449 0.977536 +vt 0.425400 0.961144 +vt 0.425649 0.978301 +vt 0.397306 0.942556 +vt 0.382211 0.947218 +vt 0.381609 0.988578 +vt 0.396562 0.993678 +vt 0.412259 0.947656 +vt 0.411657 0.989014 +vt 0.421357 0.960570 +vt 0.421130 0.976369 +vt 0.372738 0.959864 +vt 0.372511 0.975662 +vt 0.798102 0.885738 +vt 0.814208 0.917624 +vt 0.826405 0.906006 +vt 0.789242 0.887569 +vt 0.786081 0.886458 +vt 0.767554 0.919929 +vt 0.782798 0.925228 +vt 0.173513 0.155390 +vt 0.183231 0.155676 +vt 0.188423 0.147431 +vt 0.168910 0.146914 +vt 0.183467 0.139131 +vt 0.174377 0.138546 +vt 0.194991 0.179274 +vt 0.207891 0.172147 +vt 0.207779 0.158104 +vt 0.194765 0.151186 +vt 0.181864 0.158313 +vt 0.181977 0.172356 +vt 0.452325 0.997102 +vt 0.468568 0.991563 +vt 0.467918 0.946644 +vt 0.451523 0.941579 +vt 0.435280 0.947116 +vt 0.435936 0.992037 +vt 0.478205 0.960378 +vt 0.478449 0.977536 +vt 0.425400 0.961144 +vt 0.425649 0.978301 +vt 0.397306 0.942556 +vt 0.381609 0.988578 +vt 0.396562 0.993678 +vt 0.412259 0.947656 +vt 0.411657 0.989014 +vt 0.421357 0.960570 +vt 0.382211 0.947218 +vt 0.372738 0.959864 +vt 0.372511 0.975662 +vt 0.798102 0.885738 +vt 0.795787 0.887974 +vt 0.814208 0.917624 +vt 0.826405 0.906006 +vt 0.789242 0.887569 +vt 0.786081 0.886458 +vt 0.767554 0.919929 +vt 0.782798 0.925228 +vt 0.173513 0.155390 +vt 0.183231 0.155676 +vt 0.188423 0.147431 +vt 0.168910 0.146914 +vt 0.183467 0.139131 +vt 0.194991 0.179274 +vt 0.194765 0.151186 +vt 0.181864 0.158313 +vt 0.456386 0.334431 +vt 0.433241 0.334431 +vt 0.433241 0.347763 +vt 0.351838 0.334431 +vt 0.325524 0.334431 +vt 0.325524 0.347763 +vt 0.351838 0.347763 +vt 0.371053 0.334431 +vt 0.371053 0.347763 +vt 0.390364 0.334431 +vt 0.390364 0.347763 +vt 0.412002 0.334431 +vt 0.412002 0.347763 +vt 0.377220 0.266889 +vt 0.372396 0.289371 +vt 0.444407 0.289371 +vt 0.439584 0.266889 +vt 0.439584 0.306837 +vt 0.377220 0.306837 +vt 0.371053 0.334431 +vt 0.390363 0.347763 +vt 0.390363 0.334431 +vt 0.456386 0.334431 +vt 0.456386 0.347763 +vt 0.478889 0.347763 +vt 0.478889 0.334431 +vt 0.433241 0.334431 +vt 0.433241 0.347763 +vt 0.412002 0.334431 +vt 0.412002 0.347763 +vt 0.559262 0.348354 +vt 0.505244 0.347769 +vt 0.505244 0.397436 +vt 0.559262 0.397062 +vt 0.439584 0.266889 +vt 0.377220 0.266889 +vt 0.372396 0.289371 +vt 0.444407 0.289371 +vt 0.377220 0.306837 +vt 0.439584 0.306837 +vt 0.456379 0.334437 +vt 0.433234 0.334437 +vt 0.433234 0.347769 +vt 0.351830 0.334437 +vt 0.325517 0.334437 +vt 0.325517 0.347769 +vt 0.351830 0.347769 +vt 0.371046 0.334437 +vt 0.390356 0.334437 +vt 0.371046 0.347769 +vt 0.390356 0.347769 +vt 0.411994 0.334437 +vt 0.411994 0.347769 +vt 0.377213 0.266895 +vt 0.372389 0.289377 +vt 0.444399 0.289377 +vt 0.439576 0.266895 +vt 0.439576 0.306843 +vt 0.377213 0.306843 +vt 0.628535 0.029757 +vt 0.650762 0.029757 +vt 0.600970 0.032956 +vt 0.619981 0.032956 +vt 0.674432 0.029758 +vt 0.580727 0.032956 +vt 0.628535 0.029757 +vt 0.650762 0.029757 +vt 0.600970 0.032956 +vt 0.619981 0.032956 +vt 0.674432 0.029758 +vt 0.580727 0.032956 +vt 0.628535 0.029757 +vt 0.650762 0.029757 +vt 0.600970 0.032956 +vt 0.619981 0.032956 +vt 0.674432 0.029758 +vt 0.580727 0.032956 +vt 0.174822 0.136832 +vt 0.189025 0.136979 +vt 0.191327 0.128441 +vt 0.176133 0.126594 +vt 0.212013 0.185213 +vt 0.222181 0.184952 +vt 0.174829 0.136837 +vt 0.176141 0.126603 +vt 0.191329 0.128449 +vt 0.189029 0.136985 +vt 0.222174 0.184944 +vt 0.212009 0.185205 +vt 0.223976 0.196470 +vt 0.213034 0.197378 +vt 0.213019 0.206849 +vt 0.223957 0.207798 +vt 0.186358 0.196200 +vt 0.152191 0.196168 +vt 0.152185 0.207900 +vt 0.186344 0.207939 +vt 0.152191 0.196168 +vt 0.143715 0.195955 +vt 0.143715 0.208114 +vt 0.152185 0.207900 +vt 0.029076 0.989527 +vt 0.085869 0.982982 +vt 0.085950 0.973428 +vt 0.028546 0.972752 +vt 0.029073 0.919083 +vt 0.086440 0.919348 +vt 0.086530 0.909799 +vt 0.029941 0.902334 +vt 0.160624 0.973789 +vt 0.161108 0.920146 +vt 0.144866 0.920019 +vt 0.144381 0.973663 +vt 0.012803 0.918944 +vt 0.012275 0.972612 +vt 0.028546 0.972752 +vt 0.029073 0.919083 +vt 0.144866 0.920019 +vt 0.144329 0.903285 +vt 0.143542 0.990387 +vt 0.144381 0.973663 +vt 0.174822 0.136832 +vt 0.189025 0.136979 +vt 0.191327 0.128441 +vt 0.176133 0.126594 +vt 0.212013 0.185213 +vt 0.222181 0.184952 +vt 0.174829 0.136837 +vt 0.176141 0.126603 +vt 0.191329 0.128449 +vt 0.189029 0.136985 +vt 0.222174 0.184944 +vt 0.212009 0.185205 +vt 0.223976 0.196470 +vt 0.213034 0.197378 +vt 0.213019 0.206849 +vt 0.223956 0.207798 +vt 0.186358 0.196200 +vt 0.152191 0.196168 +vt 0.152185 0.207900 +vt 0.186344 0.207939 +vt 0.152191 0.196168 +vt 0.143715 0.195955 +vt 0.143715 0.208114 +vt 0.152185 0.207900 +vt 0.174822 0.136832 +vt 0.189025 0.136979 +vt 0.191327 0.128441 +vt 0.176133 0.126594 +vt 0.212009 0.185205 +vt 0.174822 0.136832 +vt 0.176133 0.126594 +vt 0.191327 0.128441 +vt 0.189025 0.136979 +vt 0.212009 0.185205 +vt 0.223974 0.196470 +vt 0.213034 0.197378 +vt 0.213019 0.206849 +vt 0.223956 0.207798 +vt 0.186358 0.196200 +vt 0.152191 0.196168 +vt 0.152185 0.207900 +vt 0.186344 0.207939 +vt 0.152191 0.196168 +vt 0.143715 0.195955 +vt 0.143715 0.208114 +vt 0.152185 0.207900 +vt 0.029076 0.989527 +vt 0.085869 0.982982 +vt 0.085950 0.973428 +vt 0.028546 0.972752 +vt 0.029073 0.919083 +vt 0.086440 0.919348 +vt 0.086530 0.909799 +vt 0.029941 0.902335 +vt 0.160624 0.973789 +vt 0.161108 0.920146 +vt 0.144866 0.920019 +vt 0.144381 0.973663 +vt 0.012803 0.918944 +vt 0.012275 0.972612 +vt 0.028546 0.972752 +vt 0.029073 0.919083 +vt 0.144866 0.920019 +vt 0.144329 0.903285 +vt 0.143542 0.990387 +vt 0.144381 0.973663 +vt 0.174822 0.136832 +vt 0.189025 0.136979 +vt 0.191327 0.128441 +vt 0.176133 0.126594 +vt 0.212009 0.185205 +vt 0.174822 0.136832 +vt 0.176133 0.126594 +vt 0.191327 0.128441 +vt 0.189025 0.136979 +vt 0.212009 0.185205 +vt 0.186358 0.196200 +vt 0.152191 0.196168 +vt 0.152185 0.207900 +vt 0.186344 0.207939 +vt 0.152191 0.196168 +vt 0.143715 0.195955 +vt 0.143715 0.208114 +vt 0.152185 0.207900 +vt 0.945111 0.584944 +vt 0.945189 0.591887 +vt 0.965441 0.590457 +vt 0.965362 0.583513 +vt 0.945111 0.584944 +vt 0.945189 0.591887 +vt 0.965441 0.590458 +vt 0.965362 0.583513 +vt 0.432887 0.915098 +vt 0.447248 0.915094 +vt 0.448154 0.849959 +vt 0.431404 0.851894 +vt 0.448393 0.833024 +vt 0.448154 0.849959 +vt 0.533533 0.849959 +vt 0.533772 0.833024 +vt 0.535639 0.764845 +vt 0.450253 0.764845 +vt 0.450110 0.828183 +vt 0.535500 0.828183 +vt 0.432886 0.915347 +vt 0.431404 0.851905 +vt 0.448154 0.849959 +vt 0.448393 0.833024 +vt 0.533772 0.833024 +vt 0.533533 0.849959 +vt 0.448154 0.849959 +vt 0.447248 0.915094 +vt 0.448154 0.849959 +vt 0.533533 0.849959 +vt 0.532626 0.915094 +vt 0.535638 0.764845 +vt 0.535498 0.828183 +vt 0.450110 0.828183 +vt 0.450254 0.764845 +vt 0.549546 0.839804 +vt 0.686557 0.839804 +vt 0.686557 0.766420 +vt 0.549546 0.766420 +vt 0.549546 0.839804 +vt 0.551589 0.891219 +vt 0.684735 0.891406 +vt 0.686557 0.839804 +vt 0.728195 0.378410 +vt 0.728242 0.345495 +vt 0.712121 0.343125 +vt 0.712069 0.380736 +vt 0.695325 0.345451 +vt 0.695278 0.378366 +vt 0.940809 0.297498 +vt 0.899813 0.300379 +vt 0.899998 0.325679 +vt 0.900832 0.373884 +vt 0.939885 0.344344 +vt 0.900142 0.343649 +vt 0.868001 0.372721 +vt 0.868660 0.345654 +vt 0.866602 0.297789 +vt 0.843504 0.298977 +vt 0.844879 0.325713 +vt 0.868319 0.324892 +vt 0.844917 0.372430 +vt 0.845249 0.345712 +vt 0.899813 0.300379 +vt 0.866602 0.297789 +vt 0.868319 0.324892 +vt 0.899998 0.325679 +vt 0.868001 0.372721 +vt 0.900142 0.343649 +vt 0.868660 0.345654 +vt 0.843504 0.298977 +vt 0.844879 0.325713 +vt 0.844917 0.372430 +vt 0.845249 0.345712 +vt 0.728195 0.378410 +vt 0.728242 0.345495 +vt 0.712121 0.343125 +vt 0.712069 0.380736 +vt 0.695325 0.345451 +vt 0.695278 0.378366 +vt 0.940809 0.297498 +vt 0.899813 0.300379 +vt 0.899998 0.325679 +vt 0.939386 0.324138 +vt 0.900832 0.373884 +vt 0.939885 0.344344 +vt 0.900142 0.343649 +vt 0.866602 0.297789 +vt 0.868319 0.324892 +vt 0.868001 0.372721 +vt 0.868660 0.345654 +vt 0.728195 0.378410 +vt 0.728242 0.345495 +vt 0.712121 0.343125 +vt 0.712069 0.380736 +vt 0.695325 0.345451 +vt 0.695278 0.378366 +vt 0.940809 0.297498 +vt 0.899813 0.300379 +vt 0.899998 0.325679 +vt 0.939386 0.324138 +vt 0.900832 0.373884 +vt 0.941715 0.371086 +vt 0.939885 0.344344 +vt 0.900142 0.343649 +vt 0.866602 0.297789 +vt 0.868319 0.324892 +vt 0.868001 0.372721 +vt 0.868660 0.345654 +vt 0.843504 0.298977 +vt 0.844879 0.325713 +vt 0.844917 0.372430 +vt 0.845249 0.345712 +vt 0.728195 0.378410 +vt 0.728242 0.345495 +vt 0.712121 0.343125 +vt 0.712069 0.380736 +vt 0.695325 0.345451 +vt 0.695278 0.378366 +vt 0.944090 0.643423 +vt 0.949905 0.657973 +vt 0.988907 0.652063 +vt 0.997019 0.642184 +vt 0.941174 0.629693 +vt 0.996136 0.628493 +vt 0.947399 0.616345 +vt 0.987859 0.615844 +vt 0.975149 0.609363 +vt 0.959784 0.608446 +vt 0.977305 0.661967 +vt 0.913207 0.453729 +vt 0.911983 0.462700 +vt 0.956080 0.463603 +vt 0.957304 0.454633 +vt 0.911983 0.462700 +vt 0.956794 0.479568 +vt 0.956080 0.463603 +vt 0.913946 0.417646 +vt 0.913480 0.437172 +vt 0.957577 0.438075 +vt 0.958044 0.418549 +vt 0.913480 0.437172 +vt 0.913207 0.453729 +vt 0.957304 0.454633 +vt 0.957577 0.438075 +vt 0.913207 0.453729 +vt 0.911983 0.462700 +vt 0.956080 0.463603 +vt 0.911983 0.462700 +vt 0.912697 0.478665 +vt 0.956794 0.479568 +vt 0.956080 0.463603 +vt 0.913946 0.417646 +vt 0.913480 0.437172 +vt 0.957577 0.438075 +vt 0.958044 0.418549 +vt 0.913480 0.437172 +vt 0.957304 0.454633 +vt 0.957577 0.438075 +vt 0.852377 0.444507 +vt 0.844290 0.440132 +vt 0.843951 0.458750 +vt 0.836033 0.445066 +vt 0.835864 0.454375 +vt 0.993792 0.684717 +vt 0.994203 0.671255 +vt 0.983798 0.664368 +vt 0.972933 0.670948 +vt 0.972512 0.684500 +vt 0.982962 0.691383 +vt 0.726423 0.719640 +vt 0.736393 0.719640 +vt 0.736392 0.711134 +vt 0.726423 0.711134 +vt 0.717259 0.719640 +vt 0.726423 0.719640 +vt 0.726423 0.711134 +vt 0.717259 0.711134 +vt 0.706925 0.719640 +vt 0.717259 0.719640 +vt 0.717259 0.711134 +vt 0.706925 0.711134 +vt 0.696485 0.719640 +vt 0.706925 0.719640 +vt 0.706925 0.711134 +vt 0.696485 0.711134 +vt 0.685626 0.719640 +vt 0.696485 0.719640 +vt 0.696485 0.711134 +vt 0.685627 0.711134 +vt 0.736393 0.719640 +vt 0.746240 0.719640 +vt 0.746240 0.711134 +vt 0.736392 0.711134 +vt 0.962377 0.382209 +vt 0.953389 0.409837 +vt 0.962378 0.409832 +vt 0.917808 0.382206 +vt 0.908728 0.382209 +vt 0.908733 0.409840 +vt 0.917813 0.409832 +vt 0.926084 0.382207 +vt 0.917808 0.382206 +vt 0.917813 0.409832 +vt 0.926087 0.409835 +vt 0.934492 0.382204 +vt 0.926084 0.382207 +vt 0.926087 0.409835 +vt 0.934492 0.409835 +vt 0.943949 0.382205 +vt 0.934492 0.382204 +vt 0.934492 0.409835 +vt 0.943950 0.409833 +vt 0.953388 0.382203 +vt 0.943949 0.382205 +vt 0.943950 0.409833 +vt 0.953389 0.409837 +vt 0.765355 0.668489 +vt 0.766762 0.663419 +vt 0.756110 0.656121 +vt 0.752188 0.659469 +vt 0.761434 0.671836 +vt 0.750782 0.664539 +vt 0.756496 0.672182 +vt 0.752426 0.669395 +vt 0.765117 0.658563 +vt 0.761048 0.655775 +vt 0.755030 0.690070 +vt 0.755121 0.691118 +vt 0.767342 0.693549 +vt 0.766882 0.688114 +vt 0.752680 0.687988 +vt 0.753539 0.688588 +vt 0.761753 0.678759 +vt 0.757282 0.675638 +vt 0.929129 0.616217 +vt 0.921653 0.618887 +vt 0.920002 0.626975 +vt 0.935022 0.621201 +vt 0.925710 0.632243 +vt 0.933476 0.629371 +vt 0.857028 0.645947 +vt 0.846384 0.658935 +vt 0.853634 0.664879 +vt 0.862578 0.661358 +vt 0.864277 0.651892 +vt 0.765355 0.668489 +vt 0.752188 0.659469 +vt 0.756110 0.656121 +vt 0.766762 0.663419 +vt 0.761434 0.671836 +vt 0.750782 0.664539 +vt 0.756496 0.672182 +vt 0.761048 0.655775 +vt 0.765117 0.658563 +vt 0.755030 0.690070 +vt 0.766882 0.688114 +vt 0.767342 0.693549 +vt 0.755121 0.691118 +vt 0.752680 0.687988 +vt 0.757282 0.675638 +vt 0.761753 0.678759 +vt 0.753539 0.688588 +vt 0.929129 0.616217 +vt 0.935022 0.621201 +vt 0.920002 0.626975 +vt 0.921653 0.618887 +vt 0.925710 0.632243 +vt 0.933476 0.629371 +vt 0.857028 0.645947 +vt 0.846384 0.658935 +vt 0.848080 0.649470 +vt 0.864277 0.651892 +vt 0.862578 0.661358 +vt 0.853634 0.664879 +vt 0.836061 0.189941 +vt 0.871098 0.177117 +vt 0.853693 0.205576 +vt 0.835760 0.168035 +vt 0.848132 0.147715 +vt 0.862902 0.142782 +vt 0.836061 0.189941 +vt 0.853693 0.205576 +vt 0.871098 0.177117 +vt 0.835760 0.168035 +vt 0.862902 0.142782 +vt 0.848132 0.147715 +vt 0.852146 0.130615 +vt 0.836061 0.189941 +vt 0.835760 0.168035 +vt 0.871098 0.177117 +vt 0.848132 0.147715 +vt 0.862902 0.142782 +vt 0.836061 0.189941 +vt 0.871098 0.177117 +vt 0.835760 0.168035 +vt 0.862902 0.142782 +vt 0.848132 0.147715 +vt 0.852146 0.130615 +vt 0.529300 0.311967 +vt 0.529108 0.297958 +vt 0.551706 0.303766 +vt 0.540575 0.321965 +vt 0.529300 0.311967 +vt 0.540575 0.321965 +vt 0.551706 0.303766 +vt 0.529108 0.297958 +vt 0.546464 0.281809 +vt 0.537019 0.284964 +vt 0.529300 0.311967 +vt 0.529108 0.297958 +vt 0.551706 0.303766 +vt 0.540575 0.321965 +vt 0.529300 0.311967 +vt 0.540575 0.321965 +vt 0.551706 0.303766 +vt 0.529108 0.297958 +vt 0.527892 0.933630 +vt 0.538348 0.933583 +vt 0.538309 0.923126 +vt 0.527846 0.923165 +vt 0.627840 0.440285 +vt 0.625545 0.417834 +vt 0.616979 0.448089 +vt 0.588185 0.439122 +vt 0.594928 0.445071 +vt 0.595805 0.411663 +vt 0.627840 0.440285 +vt 0.616979 0.448089 +vt 0.625545 0.417834 +vt 0.588185 0.439122 +vt 0.595805 0.411663 +vt 0.594928 0.445071 +vt 0.655666 0.463990 +vt 0.654573 0.448402 +vt 0.649325 0.454437 +vt 0.565258 0.430230 +vt 0.560053 0.438792 +vt 0.569873 0.436974 +vt 0.474983 0.279034 +vt 0.497509 0.277984 +vt 0.487295 0.270060 +vt 0.476619 0.318946 +vt 0.486572 0.327711 +vt 0.494340 0.318109 +vt 0.413139 0.551358 +vt 0.417025 0.526779 +vt 0.430646 0.563410 +vt 0.389300 0.557957 +vt 0.388884 0.402444 +vt 0.421084 0.413119 +vt 0.396349 0.432831 +vt 0.413139 0.551358 +vt 0.430646 0.563410 +vt 0.417025 0.526779 +vt 0.389300 0.557957 +vt 0.388884 0.402444 +vt 0.396349 0.432831 +vt 0.421084 0.413119 +vt 0.468690 0.743511 +vt 0.475418 0.747458 +vt 0.478038 0.731446 +vt 0.510176 0.746045 +vt 0.501252 0.732821 +vt 0.502587 0.749301 +vt 0.506222 0.709553 +vt 0.498085 0.704042 +vt 0.499863 0.717505 +vt 0.474427 0.709006 +vt 0.480238 0.716706 +vt 0.482262 0.703522 +vt 0.502117 0.686692 +vt 0.495074 0.681746 +vt 0.496119 0.691673 +vt 0.477232 0.686534 +vt 0.483963 0.691341 +vt 0.484430 0.681347 +vt 0.500697 0.658278 +vt 0.494735 0.654680 +vt 0.494075 0.664108 +vt 0.478454 0.657938 +vt 0.485060 0.663744 +vt 0.484474 0.654310 +vt 0.500532 0.639841 +vt 0.496369 0.635093 +vt 0.494899 0.644346 +vt 0.478967 0.639371 +vt 0.484513 0.643967 +vt 0.483350 0.634710 +vt 0.503024 0.617634 +vt 0.498736 0.615408 +vt 0.497418 0.625613 +vt 0.477373 0.617081 +vt 0.482697 0.625220 +vt 0.481834 0.614997 +vt 0.481508 0.413066 +vt 0.480532 0.414847 +vt 0.480974 0.416919 +vt 0.564173 0.517083 +vt 0.559639 0.500179 +vt 0.530028 0.514377 +vt 0.551360 0.487118 +vt 0.532862 0.478079 +vt 0.523434 0.483948 +vt 0.510684 0.484103 +vt 0.498154 0.500666 +vt 0.495473 0.519836 +vt 0.501886 0.533329 +vt 0.509988 0.544475 +vt 0.525011 0.551055 +vt 0.546770 0.546941 +vt 0.559945 0.535375 +vt 0.534909 0.661859 +vt 0.534258 0.665116 +vt 0.551174 0.674648 +vt 0.532890 0.668571 +vt 0.532254 0.671591 +vt 0.532811 0.675943 +vt 0.534498 0.679799 +vt 0.552799 0.652439 +vt 0.550855 0.652380 +vt 0.547589 0.652918 +vt 0.543878 0.653910 +vt 0.540716 0.655090 +vt 0.539240 0.656581 +vt 0.536769 0.659202 +vt 0.877978 0.856483 +vt 0.877232 0.872658 +vt 0.889929 0.851614 +vt 0.905011 0.870715 +vt 0.902126 0.972930 +vt 0.877310 0.972490 +vt 0.890365 0.983221 +vt 0.878460 0.981310 +vt 0.905096 0.852899 +vt 0.901777 0.982274 +vt 0.723741 0.946973 +vt 0.722927 0.960191 +vt 0.736016 0.945534 +vt 0.736322 0.974965 +vt 0.725525 0.973947 +vt 0.828994 0.946923 +vt 0.845428 0.959954 +vt 0.843442 0.947405 +vt 0.831004 0.976284 +vt 0.844607 0.974824 +vt 0.970981 0.973276 +vt 0.972286 0.964119 +vt 0.955902 0.974128 +vt 0.943732 0.957997 +vt 0.944710 0.863614 +vt 0.970146 0.858760 +vt 0.955654 0.852027 +vt 0.968477 0.850066 +vt 0.945665 0.969435 +vt 0.945065 0.855897 +vt 0.260362 0.824402 +vt 0.258718 0.830026 +vt 0.255089 0.827156 +vt 0.256301 0.820871 +vt 0.250601 0.822925 +vt 0.248926 0.828532 +vt 0.252981 0.832101 +vt 0.252928 0.820876 +vt 0.254644 0.815113 +vt 0.248278 0.816496 +vt 0.250476 0.811502 +vt 0.244596 0.813614 +vt 0.242846 0.819357 +vt 0.247009 0.823010 +vt 0.275191 0.846095 +vt 0.272207 0.849169 +vt 0.274373 0.844567 +vt 0.276356 0.841025 +vt 0.274536 0.839067 +vt 0.271571 0.842145 +vt 0.270407 0.847178 +vt 0.259081 0.843251 +vt 0.262060 0.840121 +vt 0.259253 0.837672 +vt 0.263258 0.834998 +vt 0.261475 0.833043 +vt 0.258515 0.836177 +vt 0.257318 0.841262 +vt 0.503285 0.920274 +vt 0.497208 0.920274 +vt 0.502118 0.935389 +vt 0.492432 0.920274 +vt 0.486946 0.920274 +vt 0.492081 0.935389 +vt 0.524836 0.920274 +vt 0.519326 0.920274 +vt 0.524730 0.935389 +vt 0.480603 0.979707 +vt 0.491431 0.994171 +vt 0.506829 0.968376 +vt 0.508694 0.999504 +vt 0.525797 0.993671 +vt 0.536202 0.978901 +vt 0.535943 0.960833 +vt 0.525109 0.946372 +vt 0.507847 0.941039 +vt 0.490749 0.946871 +vt 0.480338 0.961641 +vt 0.480603 0.979707 +vt 0.491431 0.994171 +vt 0.506829 0.968376 +vt 0.508694 0.999504 +vt 0.525797 0.993671 +vt 0.536202 0.978901 +vt 0.535943 0.960833 +vt 0.525109 0.946372 +vt 0.507847 0.941039 +vt 0.490749 0.946871 +vt 0.480338 0.961641 +vt 0.684567 0.662749 +vt 0.696486 0.646341 +vt 0.665273 0.636200 +vt 0.696486 0.626060 +vt 0.684567 0.609652 +vt 0.665273 0.603384 +vt 0.648314 0.612855 +vt 0.637833 0.627283 +vt 0.637833 0.645118 +vt 0.648314 0.659545 +vt 0.665273 0.665057 +vt 0.850508 0.375082 +vt 0.838249 0.383533 +vt 0.850252 0.386839 +vt 0.842025 0.128282 +vt 0.850396 0.112569 +vt 0.852146 0.130615 +vt 0.842025 0.128282 +vt 0.850396 0.112569 +vt 0.842025 0.128282 +vt 0.850396 0.112569 +vt 0.852146 0.130615 +vt 0.842025 0.128282 +vt 0.850396 0.112569 +vt 0.536350 0.273283 +vt 0.271518 0.856214 +vt 0.204156 0.857615 +vt 0.204156 0.899995 +vt 0.271518 0.901396 +vt 0.235704 0.841928 +vt 0.234555 0.810452 +vt 0.157310 0.809737 +vt 0.156016 0.842307 +vt 0.059247 0.588517 +vt 0.838848 0.293986 +vt 0.836522 0.271306 +vt 0.789349 0.276038 +vt 0.788994 0.292912 +vt 0.836522 0.271306 +vt 0.836139 0.250269 +vt 0.790060 0.252294 +vt 0.789349 0.276038 +vt 0.839620 0.315037 +vt 0.838848 0.293986 +vt 0.788994 0.292912 +vt 0.790838 0.315774 +vt 0.703195 0.252538 +vt 0.701350 0.278026 +vt 0.702717 0.294367 +vt 0.705239 0.318423 +vt 0.701350 0.278026 +vt 0.702717 0.294367 +vt 0.609743 0.278209 +vt 0.612827 0.250235 +vt 0.566104 0.247960 +vt 0.561762 0.274868 +vt 0.613589 0.295274 +vt 0.561153 0.299301 +vt 0.565614 0.326142 +vt 0.617149 0.323913 +vt 0.609743 0.278209 +vt 0.561762 0.274868 +vt 0.561153 0.299301 +vt 0.613589 0.295274 +vt 0.566967 0.211539 +vt 0.629108 0.214280 +vt 0.629533 0.183466 +vt 0.566967 0.182011 +vt 0.566967 0.126163 +vt 0.566967 0.155223 +vt 0.630310 0.156081 +vt 0.631134 0.127271 +vt 0.705742 0.213331 +vt 0.705984 0.185397 +vt 0.706103 0.159074 +vt 0.706733 0.130212 +vt 0.774203 0.215096 +vt 0.774444 0.185479 +vt 0.774579 0.158897 +vt 0.775057 0.130741 +vt 0.822129 0.214523 +vt 0.822129 0.186534 +vt 0.822129 0.160647 +vt 0.822129 0.132483 +vt 0.564669 0.402537 +vt 0.564669 0.415372 +vt 0.577174 0.415372 +vt 0.577174 0.402537 +vt 0.502859 0.402537 +vt 0.502859 0.415372 +vt 0.514444 0.415372 +vt 0.514444 0.402537 +vt 0.526844 0.402537 +vt 0.526844 0.415372 +vt 0.539672 0.415372 +vt 0.539672 0.402537 +vt 0.539672 0.402537 +vt 0.539672 0.415372 +vt 0.551999 0.415372 +vt 0.551999 0.402537 +vt 0.471706 0.438331 +vt 0.636580 0.476711 +vt 0.635402 0.463953 +vt 0.618635 0.472311 +vt 0.617759 0.484532 +vt 0.586407 0.477005 +vt 0.589872 0.463947 +vt 0.577521 0.450616 +vt 0.570367 0.459338 +vt 0.603285 0.468937 +vt 0.600916 0.483027 +vt 0.606649 0.448516 +vt 0.610875 0.414923 +vt 0.655666 0.463990 +vt 0.649325 0.454437 +vt 0.569873 0.436974 +vt 0.560053 0.438792 +vt 0.583448 0.435461 +vt 0.636580 0.476711 +vt 0.617759 0.484532 +vt 0.618635 0.472311 +vt 0.635402 0.463953 +vt 0.586407 0.477005 +vt 0.570367 0.459338 +vt 0.577521 0.450616 +vt 0.589872 0.463947 +vt 0.600916 0.483027 +vt 0.603285 0.468937 +vt 0.606649 0.448516 +vt 0.610875 0.414923 +vt 0.649325 0.454437 +vt 0.655666 0.463990 +vt 0.560053 0.438792 +vt 0.569873 0.436974 +vt 0.583448 0.435461 +vt 0.644722 0.427325 +vt 0.626988 0.414531 +vt 0.625545 0.417834 +vt 0.627840 0.440285 +vt 0.651492 0.439741 +vt 0.638135 0.450043 +vt 0.576238 0.424739 +vt 0.583448 0.435461 +vt 0.645320 0.557037 +vt 0.628080 0.557037 +vt 0.526991 0.426368 +vt 0.517179 0.429381 +vt 0.520509 0.440363 +vt 0.526439 0.438091 +vt 0.517179 0.429381 +vt 0.508314 0.436417 +vt 0.515598 0.445643 +vt 0.520509 0.440363 +vt 0.508314 0.436417 +vt 0.501521 0.444148 +vt 0.512137 0.450798 +vt 0.515598 0.445643 +vt 0.550834 0.446378 +vt 0.544670 0.438003 +vt 0.536515 0.446600 +vt 0.539503 0.452135 +vt 0.544670 0.438003 +vt 0.536437 0.430110 +vt 0.532131 0.440845 +vt 0.536515 0.446600 +vt 0.536437 0.430110 +vt 0.526991 0.426368 +vt 0.526439 0.438091 +vt 0.532131 0.440845 +vt 0.596075 0.350114 +vt 0.595279 0.364093 +vt 0.616080 0.372041 +vt 0.619586 0.360479 +vt 0.595279 0.364093 +vt 0.596663 0.387825 +vt 0.614474 0.383641 +vt 0.616080 0.372041 +vt 0.596663 0.387825 +vt 0.596749 0.401610 +vt 0.615443 0.396067 +vt 0.614474 0.383641 +vt 0.596075 0.350114 +vt 0.595279 0.364093 +vt 0.576423 0.367054 +vt 0.573305 0.354868 +vt 0.595279 0.364093 +vt 0.596662 0.387825 +vt 0.579220 0.380289 +vt 0.576423 0.367054 +vt 0.596662 0.387825 +vt 0.596749 0.401610 +vt 0.578360 0.393861 +vt 0.579220 0.380289 +vt 0.663136 0.518737 +vt 0.663623 0.527478 +vt 0.682644 0.526379 +vt 0.682199 0.518654 +vt 0.663623 0.527478 +vt 0.664859 0.537528 +vt 0.683664 0.535253 +vt 0.682644 0.526379 +vt 0.664859 0.537528 +vt 0.665810 0.546416 +vt 0.684821 0.542976 +vt 0.683664 0.535253 +vt 0.665566 0.491283 +vt 0.664716 0.500080 +vt 0.683415 0.502134 +vt 0.684459 0.494459 +vt 0.664716 0.500080 +vt 0.663572 0.510028 +vt 0.682527 0.510949 +vt 0.683415 0.502134 +vt 0.663572 0.510028 +vt 0.663136 0.518737 +vt 0.682199 0.518654 +vt 0.682527 0.510949 +vt 0.650936 0.398976 +vt 0.674530 0.408171 +vt 0.677767 0.395266 +vt 0.652159 0.388592 +vt 0.650936 0.398976 +vt 0.648781 0.407089 +vt 0.670365 0.420597 +vt 0.674530 0.408171 +vt 0.648387 0.353084 +vt 0.650319 0.360342 +vt 0.670686 0.351762 +vt 0.666825 0.341071 +vt 0.650319 0.360342 +vt 0.651689 0.369751 +vt 0.674400 0.363557 +vt 0.670686 0.351762 +vt 0.651689 0.369751 +vt 0.652289 0.378723 +vt 0.676921 0.372844 +vt 0.674400 0.363557 +vt 0.652289 0.378723 +vt 0.652159 0.388592 +vt 0.677767 0.395266 +vt 0.676921 0.372844 +vt 0.592214 0.524960 +vt 0.590939 0.534915 +vt 0.621449 0.540027 +vt 0.620558 0.528554 +vt 0.590939 0.534915 +vt 0.589752 0.547128 +vt 0.622143 0.550196 +vt 0.621449 0.540027 +vt 0.594670 0.490345 +vt 0.594752 0.498591 +vt 0.622521 0.497268 +vt 0.623368 0.487949 +vt 0.594752 0.498591 +vt 0.594036 0.507860 +vt 0.621199 0.507995 +vt 0.622521 0.497268 +vt 0.594036 0.507860 +vt 0.593124 0.516304 +vt 0.620347 0.518145 +vt 0.621199 0.507995 +vt 0.592214 0.524960 +vt 0.620558 0.528554 +vt 0.620347 0.518145 +vt 0.593124 0.516304 +vt 0.622910 0.364541 +vt 0.576422 0.367054 +vt 0.579220 0.380289 +vt 0.579220 0.380289 +vt 0.578360 0.393861 +vt 0.619586 0.360479 +vt 0.616080 0.372041 +vt 0.616080 0.372041 +vt 0.614475 0.383641 +vt 0.614475 0.383641 +vt 0.615443 0.396067 +vt 0.576422 0.367054 +vt 0.573306 0.354868 +vt 0.504364 0.430179 +vt 0.515502 0.422763 +vt 0.495900 0.438403 +vt 0.504364 0.430179 +vt 0.549155 0.432091 +vt 0.556986 0.441053 +vt 0.538674 0.423660 +vt 0.549155 0.432091 +vt 0.538674 0.423660 +vt 0.527252 0.419817 +vt 0.515502 0.422763 +vt 0.527252 0.419817 +vt 0.611507 0.577419 +vt 0.682203 0.577419 +vt 0.579433 0.565670 +vt 0.631173 0.386461 +vt 0.628519 0.386594 +vt 0.627696 0.394707 +vt 0.630349 0.395717 +vt 0.677767 0.395266 +vt 0.674530 0.408171 +vt 0.678766 0.408335 +vt 0.682670 0.395377 +vt 0.625912 0.401585 +vt 0.628684 0.404191 +vt 0.670365 0.420597 +vt 0.675939 0.418930 +vt 0.629771 0.355089 +vt 0.627119 0.357395 +vt 0.628511 0.363821 +vt 0.631029 0.362969 +vt 0.666825 0.341071 +vt 0.670686 0.351762 +vt 0.674262 0.351414 +vt 0.671635 0.342403 +vt 0.628946 0.371478 +vt 0.631482 0.371692 +vt 0.674400 0.363557 +vt 0.678063 0.361880 +vt 0.628892 0.378943 +vt 0.631482 0.378980 +vt 0.676921 0.372844 +vt 0.682244 0.374159 +vt 0.645908 0.527751 +vt 0.646756 0.537356 +vt 0.646756 0.537356 +vt 0.647554 0.545266 +vt 0.647633 0.492680 +vt 0.646885 0.500323 +vt 0.646885 0.500323 +vt 0.646005 0.509698 +vt 0.646005 0.509698 +vt 0.645609 0.518685 +vt 0.645609 0.518685 +vt 0.645908 0.527751 +vt 0.631173 0.386461 +vt 0.630349 0.395717 +vt 0.630349 0.395717 +vt 0.628684 0.404191 +vt 0.629771 0.355089 +vt 0.631029 0.362969 +vt 0.631029 0.362969 +vt 0.631482 0.371692 +vt 0.631482 0.371692 +vt 0.631482 0.378980 +vt 0.631482 0.378980 +vt 0.631173 0.386461 +vt 0.487257 0.473748 +vt 0.485903 0.460807 +vt 0.449069 0.329959 +vt 0.448603 0.316962 +vt 0.448603 0.316962 +vt 0.476619 0.318946 +vt 0.474983 0.279034 +vt 0.448902 0.277481 +vt 0.448902 0.277481 +vt 0.450991 0.265335 +vt 0.494340 0.318109 +vt 0.497509 0.277984 +vt 0.514716 0.278513 +vt 0.512667 0.266454 +vt 0.512826 0.330007 +vt 0.514338 0.317901 +vt 0.514338 0.317901 +vt 0.514716 0.278513 +vt 0.344424 0.520271 +vt 0.344424 0.520271 +vt 0.361352 0.520540 +vt 0.358009 0.500349 +vt 0.344818 0.501772 +vt 0.378054 0.519648 +vt 0.377081 0.501295 +vt 0.358009 0.500349 +vt 0.361352 0.520540 +vt 0.324464 0.502603 +vt 0.324714 0.520542 +vt 0.344424 0.520271 +vt 0.344818 0.501772 +vt 0.322774 0.478356 +vt 0.342683 0.476976 +vt 0.378120 0.476568 +vt 0.361217 0.475862 +vt 0.361217 0.475862 +vt 0.342683 0.476976 +vt 0.361352 0.520540 +vt 0.344424 0.520271 +vt 0.344818 0.501772 +vt 0.358009 0.500349 +vt 0.342683 0.476976 +vt 0.324714 0.520542 +vt 0.324464 0.502603 +vt 0.344818 0.501772 +vt 0.344424 0.520271 +vt 0.377081 0.501295 +vt 0.378054 0.519648 +vt 0.361352 0.520540 +vt 0.358009 0.500349 +vt 0.342683 0.476976 +vt 0.361217 0.475862 +vt 0.378120 0.476568 +vt 0.361217 0.475862 +vt 0.322774 0.478356 +vt 0.342683 0.476976 +vt 0.222063 0.640983 +vt 0.232602 0.641656 +vt 0.229869 0.626101 +vt 0.224076 0.625716 +vt 0.232602 0.641656 +vt 0.242917 0.638984 +vt 0.237795 0.625314 +vt 0.229869 0.626101 +vt 0.211398 0.638579 +vt 0.222063 0.640983 +vt 0.224076 0.625716 +vt 0.216061 0.625036 +vt 0.231040 0.609000 +vt 0.223056 0.609127 +vt 0.223056 0.609127 +vt 0.215592 0.609352 +vt 0.238469 0.609318 +vt 0.231040 0.609000 +vt 0.219438 0.583879 +vt 0.232442 0.591968 +vt 0.241387 0.594449 +vt 0.242833 0.589244 +vt 0.233884 0.586760 +vt 0.308714 0.586081 +vt 0.324357 0.638458 +vt 0.323444 0.627585 +vt 0.312039 0.627996 +vt 0.311729 0.634709 +vt 0.323444 0.627585 +vt 0.321996 0.615624 +vt 0.311409 0.619644 +vt 0.312039 0.627996 +vt 0.303156 0.626893 +vt 0.301772 0.638365 +vt 0.303036 0.619791 +vt 0.303156 0.626893 +vt 0.311729 0.634709 +vt 0.301772 0.638365 +vt 0.302390 0.645062 +vt 0.310686 0.643271 +vt 0.321144 0.647155 +vt 0.324357 0.638458 +vt 0.394498 0.609284 +vt 0.399013 0.589480 +vt 0.380656 0.590156 +vt 0.378793 0.606734 +vt 0.012612 0.615551 +vt 0.032947 0.613055 +vt 0.032996 0.596849 +vt 0.016472 0.597515 +vt 0.304228 0.684120 +vt 0.324279 0.687030 +vt 0.320886 0.671506 +vt 0.304361 0.670512 +vt 0.358383 0.584027 +vt 0.358855 0.609095 +vt 0.324497 0.651731 +vt 0.304580 0.655376 +vt 0.033075 0.578771 +vt 0.013316 0.574716 +vt 0.748784 0.691477 +vt 0.748480 0.702196 +vt 0.659803 0.698968 +vt 0.659783 0.688498 +vt 0.747992 0.664740 +vt 0.748398 0.675457 +vt 0.660428 0.678715 +vt 0.660352 0.668247 +vt 0.579945 0.676595 +vt 0.580222 0.666178 +vt 0.578935 0.701757 +vt 0.578565 0.691342 +vt 0.748784 0.691477 +vt 0.748784 0.691477 +vt 0.659783 0.688498 +vt 0.659803 0.698968 +vt 0.748480 0.702196 +vt 0.747992 0.664740 +vt 0.660352 0.668247 +vt 0.660428 0.678715 +vt 0.748398 0.675457 +vt 0.580222 0.666178 +vt 0.579945 0.676595 +vt 0.578565 0.691342 +vt 0.578935 0.701757 +vt 0.394499 0.609285 +vt 0.378793 0.606735 +vt 0.380656 0.590154 +vt 0.399014 0.589479 +vt 0.012613 0.615550 +vt 0.016473 0.597515 +vt 0.032996 0.596849 +vt 0.032945 0.613053 +vt 0.304227 0.684121 +vt 0.304360 0.670511 +vt 0.320886 0.671506 +vt 0.324279 0.687032 +vt 0.358854 0.609097 +vt 0.358382 0.584027 +vt 0.304578 0.655375 +vt 0.324496 0.651730 +vt 0.013317 0.574717 +vt 0.033075 0.578772 +vt 0.397326 0.525455 +vt 0.377378 0.549829 +vt 0.382728 0.483359 +vt 0.404267 0.484518 +vt 0.391658 0.460595 +vt 0.412854 0.460201 +vt 0.375531 0.411402 +vt 0.379646 0.431434 +vt 0.464447 0.403667 +vt 0.464447 0.422788 +vt 0.464447 0.453096 +vt 0.464447 0.481306 +vt 0.464447 0.522919 +vt 0.464447 0.569581 +vt 0.397326 0.525455 +vt 0.377378 0.549829 +vt 0.389300 0.557957 +vt 0.382728 0.483359 +vt 0.404267 0.484518 +vt 0.391658 0.460595 +vt 0.412854 0.460201 +vt 0.375531 0.411402 +vt 0.379646 0.431434 +vt 0.464447 0.422788 +vt 0.464447 0.403667 +vt 0.464447 0.481306 +vt 0.464447 0.453096 +vt 0.464447 0.522919 +vt 0.464447 0.569581 +vt 0.010190 0.305514 +vt 0.535133 0.601239 +vt 0.460008 0.693674 +vt 0.460009 0.657172 +vt 0.442813 0.657173 +vt 0.442813 0.693674 +vt 0.460009 0.657172 +vt 0.460009 0.632714 +vt 0.442813 0.632714 +vt 0.442813 0.657173 +vt 0.460009 0.612313 +vt 0.442813 0.612313 +vt 0.669282 0.581740 +vt 0.651918 0.581730 +vt 0.651909 0.597528 +vt 0.669273 0.597537 +vt 0.636647 0.581726 +vt 0.636638 0.597515 +vt 0.460008 0.751131 +vt 0.442812 0.751130 +vt 0.460009 0.657172 +vt 0.460008 0.693674 +vt 0.442813 0.693674 +vt 0.442813 0.657173 +vt 0.460009 0.632714 +vt 0.460009 0.657172 +vt 0.442813 0.657173 +vt 0.442813 0.632714 +vt 0.576483 0.581689 +vt 0.588662 0.581696 +vt 0.588654 0.597490 +vt 0.576476 0.597484 +vt 0.651918 0.581730 +vt 0.669282 0.581740 +vt 0.669273 0.597537 +vt 0.651909 0.597528 +vt 0.460009 0.612313 +vt 0.442813 0.612313 +vt 0.636647 0.581726 +vt 0.636638 0.597515 +vt 0.460008 0.751131 +vt 0.442812 0.751130 +vt 0.487466 0.581644 +vt 0.554962 0.581699 +vt 0.554955 0.597452 +vt 0.487458 0.597440 +vt 0.783360 0.652036 +vt 0.812776 0.683445 +vt 0.813882 0.685355 +vt 0.818483 0.683168 +vt 0.816824 0.680039 +vt 0.814511 0.687440 +vt 0.819374 0.686466 +vt 0.814662 0.689588 +vt 0.819492 0.689809 +vt 0.814439 0.691373 +vt 0.818891 0.692930 +vt 0.813712 0.693053 +vt 0.817568 0.695812 +vt 0.812567 0.694474 +vt 0.815588 0.698344 +vt 0.811143 0.695494 +vt 0.813017 0.700465 +vt 0.809581 0.695998 +vt 0.809852 0.702204 +vt 0.808041 0.695903 +vt 0.805861 0.703835 +vt 0.805637 0.680477 +vt 0.807485 0.680282 +vt 0.807391 0.672942 +vt 0.802386 0.671108 +vt 0.809413 0.680776 +vt 0.811276 0.674891 +vt 0.811241 0.681879 +vt 0.814402 0.677265 +vt 0.823809 0.680791 +vt 0.821558 0.676344 +vt 0.824941 0.685510 +vt 0.824999 0.690208 +vt 0.824005 0.694745 +vt 0.822035 0.698904 +vt 0.819161 0.702605 +vt 0.815456 0.705788 +vt 0.810935 0.708504 +vt 0.805497 0.710831 +vt 0.808411 0.665482 +vt 0.801715 0.662733 +vt 0.813854 0.668651 +vt 0.818242 0.672245 +vt 0.830202 0.678209 +vt 0.827367 0.672210 +vt 0.831594 0.684488 +vt 0.831524 0.690843 +vt 0.830086 0.696914 +vt 0.827377 0.702496 +vt 0.823544 0.707391 +vt 0.818698 0.711590 +vt 0.812929 0.715186 +vt 0.806296 0.718397 +vt 0.810670 0.657642 +vt 0.802657 0.653692 +vt 0.817509 0.661920 +vt 0.823095 0.666781 +vt 0.838055 0.675248 +vt 0.834566 0.667575 +vt 0.839666 0.683448 +vt 0.839417 0.691721 +vt 0.837409 0.699631 +vt 0.833841 0.706759 +vt 0.828920 0.712903 +vt 0.822842 0.718003 +vt 0.815778 0.722248 +vt 0.808002 0.726193 +vt 0.814102 0.649457 +vt 0.804901 0.644510 +vt 0.822376 0.654653 +vt 0.829290 0.660653 +vt 0.847724 0.671826 +vt 0.843520 0.662151 +vt 0.849553 0.682315 +vt 0.849007 0.692983 +vt 0.846253 0.703090 +vt 0.841608 0.712018 +vt 0.835426 0.719424 +vt 0.827944 0.725250 +vt 0.819320 0.729601 +vt 0.809824 0.732673 +vt 0.818443 0.641044 +vt 0.807332 0.637017 +vt 0.828578 0.646584 +vt 0.837071 0.653672 +vt 0.478038 0.731446 +vt 0.475418 0.747458 +vt 0.502587 0.749301 +vt 0.501252 0.732821 +vt 0.499863 0.717505 +vt 0.480238 0.716706 +vt 0.498085 0.704042 +vt 0.482262 0.703522 +vt 0.496119 0.691673 +vt 0.483963 0.691341 +vt 0.495074 0.681746 +vt 0.484430 0.681347 +vt 0.494638 0.672668 +vt 0.484639 0.672262 +vt 0.494075 0.664108 +vt 0.485060 0.663744 +vt 0.494735 0.654680 +vt 0.484474 0.654310 +vt 0.494899 0.644346 +vt 0.484513 0.643967 +vt 0.496369 0.635093 +vt 0.483350 0.634710 +vt 0.497418 0.625613 +vt 0.482697 0.625220 +vt 0.498736 0.615408 +vt 0.481834 0.614997 +vt 0.500869 0.672188 +vt 0.494638 0.672668 +vt 0.478404 0.671920 +vt 0.484639 0.672262 +vt 0.882516 0.759686 +vt 0.897602 0.759686 +vt 0.894575 0.739550 +vt 0.880596 0.739550 +vt 0.912157 0.759686 +vt 0.908064 0.739550 +vt 0.926600 0.759686 +vt 0.921447 0.739550 +vt 0.939670 0.759686 +vt 0.933559 0.739550 +vt 0.772985 0.759686 +vt 0.786521 0.759686 +vt 0.791639 0.739550 +vt 0.779096 0.739550 +vt 0.800131 0.759686 +vt 0.804252 0.739550 +vt 0.812358 0.759686 +vt 0.815583 0.739550 +vt 0.825814 0.759686 +vt 0.828051 0.739550 +vt 0.839090 0.759686 +vt 0.840355 0.739550 +vt 0.853467 0.759686 +vt 0.853677 0.739550 +vt 0.868155 0.759686 +vt 0.867288 0.739550 +vt 0.492522 0.405842 +vt 0.488491 0.410470 +vt 0.490537 0.415106 +vt 0.495655 0.414867 +vt 0.483134 0.401702 +vt 0.483671 0.408808 +vt 0.473015 0.405270 +vt 0.478481 0.409747 +vt 0.469851 0.414277 +vt 0.476164 0.414487 +vt 0.472516 0.422852 +vt 0.477526 0.419689 +vt 0.479280 0.427938 +vt 0.481529 0.422787 +vt 0.487217 0.427894 +vt 0.486123 0.422698 +vt 0.493431 0.422925 +vt 0.489479 0.419817 +vt 0.490537 0.415106 +vt 0.485389 0.413277 +vt 0.486353 0.415094 +vt 0.483464 0.412509 +vt 0.482561 0.418274 +vt 0.484524 0.418317 +vt 0.489479 0.419817 +vt 0.485997 0.417071 +vt 0.588463 0.518375 +vt 0.582757 0.491275 +vt 0.570695 0.495881 +vt 0.575900 0.516719 +vt 0.566828 0.468381 +vt 0.559197 0.477783 +vt 0.535401 0.454759 +vt 0.532256 0.465815 +vt 0.535401 0.454759 +vt 0.516879 0.456777 +vt 0.518735 0.470859 +vt 0.532256 0.465815 +vt 0.499636 0.466084 +vt 0.505757 0.472895 +vt 0.499636 0.466084 +vt 0.473138 0.492132 +vt 0.487052 0.495200 +vt 0.505757 0.472895 +vt 0.470232 0.524442 +vt 0.483049 0.521494 +vt 0.481714 0.551020 +vt 0.492030 0.541298 +vt 0.495590 0.565471 +vt 0.502472 0.553718 +vt 0.521148 0.577834 +vt 0.523453 0.564415 +vt 0.558234 0.570570 +vt 0.554978 0.558762 +vt 0.580638 0.549861 +vt 0.570313 0.541447 +vt 0.575900 0.516719 +vt 0.570695 0.495881 +vt 0.564829 0.497612 +vt 0.569874 0.516277 +vt 0.559197 0.477783 +vt 0.555378 0.482994 +vt 0.532256 0.465815 +vt 0.532035 0.473987 +vt 0.518735 0.470859 +vt 0.522213 0.478517 +vt 0.505757 0.472895 +vt 0.508946 0.479077 +vt 0.487052 0.495200 +vt 0.495046 0.497752 +vt 0.483049 0.521494 +vt 0.489829 0.519888 +vt 0.492030 0.541298 +vt 0.496198 0.535370 +vt 0.502472 0.553718 +vt 0.506121 0.548829 +vt 0.523453 0.564415 +vt 0.524733 0.556142 +vt 0.554978 0.558762 +vt 0.550366 0.551457 +vt 0.570313 0.541447 +vt 0.565224 0.537461 +vt 0.569874 0.516277 +vt 0.564829 0.497612 +vt 0.559639 0.500179 +vt 0.564173 0.517083 +vt 0.555378 0.482994 +vt 0.551360 0.487118 +vt 0.532035 0.473987 +vt 0.532862 0.478079 +vt 0.532035 0.473987 +vt 0.522213 0.478517 +vt 0.523434 0.483948 +vt 0.532862 0.478079 +vt 0.508946 0.479077 +vt 0.510684 0.484103 +vt 0.508946 0.479077 +vt 0.495046 0.497752 +vt 0.498154 0.500666 +vt 0.510684 0.484103 +vt 0.489829 0.519888 +vt 0.495473 0.519836 +vt 0.496198 0.535370 +vt 0.501886 0.533329 +vt 0.506121 0.548829 +vt 0.509988 0.544475 +vt 0.524733 0.556142 +vt 0.525011 0.551055 +vt 0.550366 0.551457 +vt 0.546770 0.546941 +vt 0.565224 0.537461 +vt 0.559945 0.535375 +vt 0.367978 0.622362 +vt 0.370326 0.670884 +vt 0.354199 0.672557 +vt 0.349318 0.623464 +vt 0.375342 0.711606 +vt 0.360147 0.711872 +vt 0.358820 0.757868 +vt 0.345021 0.748431 +vt 0.342426 0.802453 +vt 0.325386 0.791696 +vt 0.765206 0.813032 +vt 0.741407 0.764660 +vt 0.727179 0.778369 +vt 0.746954 0.819947 +vt 0.611815 0.939264 +vt 0.611602 0.906953 +vt 0.641326 0.908115 +vt 0.641490 0.939059 +vt 0.420575 0.670676 +vt 0.370326 0.670884 +vt 0.367978 0.622362 +vt 0.420456 0.622477 +vt 0.666350 0.911004 +vt 0.666413 0.938950 +vt 0.420661 0.711339 +vt 0.375342 0.711606 +vt 0.687944 0.900943 +vt 0.687880 0.938966 +vt 0.420681 0.757066 +vt 0.358820 0.757868 +vt 0.714393 0.889311 +vt 0.714168 0.939083 +vt 0.420616 0.803564 +vt 0.342426 0.802453 +vt 0.726912 0.840712 +vt 0.746954 0.819947 +vt 0.727179 0.778369 +vt 0.693998 0.819251 +vt 0.843397 0.766143 +vt 0.741407 0.764660 +vt 0.765206 0.813032 +vt 0.842668 0.814157 +vt 0.420952 0.813491 +vt 0.420519 0.919310 +vt 0.342421 0.919310 +vt 0.342421 0.813491 +vt 0.342421 0.813491 +vt 0.342421 0.919310 +vt 0.325579 0.919310 +vt 0.325562 0.813491 +vt 0.321004 0.848879 +vt 0.320796 0.903364 +vt 0.278067 0.903008 +vt 0.278421 0.848669 +vt 0.367977 0.622359 +vt 0.349318 0.623464 +vt 0.354199 0.672557 +vt 0.370326 0.670884 +vt 0.360147 0.711871 +vt 0.375344 0.711606 +vt 0.345021 0.748431 +vt 0.358820 0.757868 +vt 0.325386 0.791696 +vt 0.342426 0.802456 +vt 0.765206 0.813032 +vt 0.746954 0.819947 +vt 0.727179 0.778369 +vt 0.741407 0.764661 +vt 0.611815 0.939264 +vt 0.641490 0.939059 +vt 0.641326 0.908115 +vt 0.611602 0.906953 +vt 0.420575 0.670676 +vt 0.420456 0.622477 +vt 0.367977 0.622359 +vt 0.370326 0.670884 +vt 0.666413 0.938950 +vt 0.666350 0.911004 +vt 0.420661 0.711339 +vt 0.375344 0.711606 +vt 0.687880 0.938966 +vt 0.687944 0.900943 +vt 0.420681 0.757066 +vt 0.358820 0.757868 +vt 0.714168 0.939083 +vt 0.714393 0.889311 +vt 0.420616 0.803564 +vt 0.342426 0.802456 +vt 0.726912 0.840712 +vt 0.693998 0.819251 +vt 0.727179 0.778369 +vt 0.746954 0.819947 +vt 0.843397 0.766143 +vt 0.842668 0.814157 +vt 0.765206 0.813032 +vt 0.741407 0.764661 +vt 0.420952 0.813491 +vt 0.342421 0.813491 +vt 0.342421 0.813491 +vt 0.325574 0.813491 +vt 0.325591 0.919310 +vt 0.321004 0.848879 +vt 0.278421 0.848669 +vt 0.928846 0.238454 +vt 0.955163 0.238449 +vt 0.958342 0.201882 +vt 0.925667 0.201882 +vt 0.928846 0.238452 +vt 0.955163 0.238456 +vt 0.958342 0.201882 +vt 0.925667 0.201882 +vt 0.928846 0.238452 +vt 0.955163 0.238456 +vt 0.958342 0.201882 +vt 0.925667 0.201882 +vt 0.928846 0.238452 +vt 0.955163 0.238456 +vt 0.958342 0.201882 +vt 0.925667 0.201882 +vt 0.928846 0.238452 +vt 0.955163 0.238456 +vt 0.958342 0.201882 +vt 0.925667 0.201882 +vt 0.928846 0.238452 +vt 0.955163 0.238456 +vt 0.958342 0.201882 +vt 0.925667 0.201882 +vt 0.955119 0.169575 +vt 0.928798 0.169575 +vt 0.955119 0.169575 +vt 0.928798 0.169575 +vt 0.955119 0.169575 +vt 0.928798 0.169575 +vt 0.955119 0.169575 +vt 0.928798 0.169575 +vt 0.955119 0.169575 +vt 0.928798 0.169575 +vt 0.955119 0.169575 +vt 0.928798 0.169575 +vt 0.293485 0.577328 +vt 0.255393 0.577328 +vt 0.250861 0.624083 +vt 0.298149 0.624083 +vt 0.255462 0.677010 +vt 0.293548 0.677003 +vt 0.298149 0.624083 +vt 0.250861 0.624083 +vt 0.255462 0.677010 +vt 0.293548 0.677003 +vt 0.298149 0.624083 +vt 0.250861 0.624083 +vt 0.255462 0.677010 +vt 0.293548 0.677003 +vt 0.298149 0.624083 +vt 0.250861 0.624083 +vt 0.255462 0.677010 +vt 0.293548 0.677003 +vt 0.298149 0.624083 +vt 0.250861 0.624083 +vt 0.255462 0.677010 +vt 0.293548 0.677003 +vt 0.298149 0.624083 +vt 0.250861 0.624083 +vt 0.293485 0.577328 +vt 0.255393 0.577328 +vt 0.293485 0.577328 +vt 0.255393 0.577328 +vt 0.293485 0.577328 +vt 0.255393 0.577328 +vt 0.255462 0.677010 +vt 0.293548 0.677003 +vt 0.293485 0.577328 +vt 0.255393 0.577328 +vt 0.293485 0.577328 +vt 0.255393 0.577328 +vt 0.595373 0.720752 +vt 0.595373 0.727266 +vt 0.599532 0.727266 +vt 0.599532 0.720752 +vt 0.599532 0.713925 +vt 0.595373 0.713925 +vt 0.856994 0.676371 +vt 0.856994 0.692316 +vt 0.865952 0.692316 +vt 0.865952 0.676370 +vt 0.856993 0.669547 +vt 0.865952 0.669547 +vt 0.561124 0.713515 +vt 0.561124 0.720739 +vt 0.566061 0.720739 +vt 0.566062 0.713515 +vt 0.561124 0.707194 +vt 0.566061 0.707194 +vt 0.599532 0.720752 +vt 0.599532 0.727266 +vt 0.603626 0.727267 +vt 0.603626 0.720752 +vt 0.603626 0.713925 +vt 0.599532 0.713925 +vt 0.865952 0.676370 +vt 0.865952 0.692316 +vt 0.874771 0.692316 +vt 0.874771 0.676370 +vt 0.874771 0.669547 +vt 0.865952 0.669547 +vt 0.566062 0.713515 +vt 0.566061 0.720739 +vt 0.570922 0.720739 +vt 0.570922 0.713515 +vt 0.566061 0.707194 +vt 0.570922 0.707194 +vt 0.603626 0.720752 +vt 0.603626 0.727267 +vt 0.607882 0.727266 +vt 0.607882 0.720752 +vt 0.603626 0.713925 +vt 0.607882 0.713925 +vt 0.874771 0.676370 +vt 0.874771 0.692316 +vt 0.883941 0.692316 +vt 0.883941 0.676370 +vt 0.874771 0.669547 +vt 0.883941 0.669547 +vt 0.570922 0.720739 +vt 0.575975 0.720739 +vt 0.575975 0.713515 +vt 0.575975 0.707194 +vt 0.612524 0.713925 +vt 0.607882 0.713925 +vt 0.607882 0.720752 +vt 0.612524 0.720752 +vt 0.607882 0.727266 +vt 0.612524 0.727266 +vt 0.893936 0.692316 +vt 0.893938 0.676370 +vt 0.893936 0.669547 +vt 0.883941 0.669547 +vt 0.575975 0.713515 +vt 0.575975 0.720739 +vt 0.581485 0.720739 +vt 0.581485 0.713515 +vt 0.575975 0.707194 +vt 0.581485 0.707194 +vt 0.612524 0.720752 +vt 0.612524 0.727266 +vt 0.616770 0.727266 +vt 0.616770 0.720752 +vt 0.616770 0.713925 +vt 0.612524 0.713925 +vt 0.893938 0.676370 +vt 0.893936 0.692316 +vt 0.903084 0.692316 +vt 0.903084 0.676370 +vt 0.903084 0.669547 +vt 0.893936 0.669547 +vt 0.581485 0.713515 +vt 0.581485 0.720739 +vt 0.586525 0.720739 +vt 0.586525 0.713515 +vt 0.581485 0.707194 +vt 0.586526 0.707194 +vt 0.621440 0.713925 +vt 0.616770 0.713925 +vt 0.616770 0.720752 +vt 0.621440 0.720752 +vt 0.913145 0.676371 +vt 0.903084 0.676370 +vt 0.903084 0.692316 +vt 0.913145 0.692316 +vt 0.913145 0.669547 +vt 0.903084 0.669547 +vt 0.592071 0.713515 +vt 0.586525 0.713515 +vt 0.586525 0.720739 +vt 0.592071 0.720739 +vt 0.592071 0.707194 +vt 0.586526 0.707194 +vt 0.561124 0.726221 +vt 0.566062 0.726221 +vt 0.586526 0.726221 +vt 0.592071 0.726221 +vt 0.581485 0.726221 +vt 0.586526 0.726221 +vt 0.575975 0.726221 +vt 0.581485 0.726221 +vt 0.570922 0.726221 +vt 0.575975 0.726221 +vt 0.566062 0.726221 +vt 0.570922 0.726221 +vt 0.592071 0.707194 +vt 0.592071 0.705581 +vt 0.586525 0.705581 +vt 0.586526 0.707194 +vt 0.561124 0.707194 +vt 0.566061 0.707194 +vt 0.566061 0.705580 +vt 0.561124 0.705581 +vt 0.570922 0.707194 +vt 0.570922 0.705581 +vt 0.575975 0.705580 +vt 0.575975 0.707194 +vt 0.581485 0.707194 +vt 0.581485 0.705581 +vt 0.561124 0.726221 +vt 0.561124 0.727869 +vt 0.566062 0.727869 +vt 0.566062 0.726221 +vt 0.592071 0.726221 +vt 0.586526 0.726221 +vt 0.586525 0.727869 +vt 0.592071 0.727869 +vt 0.581485 0.726221 +vt 0.581485 0.727869 +vt 0.575975 0.726221 +vt 0.575975 0.727869 +vt 0.570922 0.727869 +vt 0.570922 0.726221 +vt 0.747637 0.758623 +vt 0.756599 0.758623 +vt 0.756599 0.747687 +vt 0.747637 0.747687 +vt 0.634474 0.758623 +vt 0.645304 0.758623 +vt 0.645304 0.747687 +vt 0.634474 0.747687 +vt 0.654934 0.758623 +vt 0.654934 0.747687 +vt 0.665270 0.758623 +vt 0.665270 0.747687 +vt 0.675491 0.758623 +vt 0.675491 0.747687 +vt 0.685636 0.758623 +vt 0.685636 0.747687 +vt 0.695509 0.758623 +vt 0.695509 0.747687 +vt 0.705178 0.758623 +vt 0.705178 0.747687 +vt 0.716021 0.758623 +vt 0.716021 0.747687 +vt 0.726917 0.758623 +vt 0.726917 0.747687 +vt 0.737640 0.758623 +vt 0.737640 0.747687 +vt 0.699611 0.737629 +vt 0.709390 0.737629 +vt 0.709390 0.728639 +vt 0.699611 0.728639 +vt 0.690890 0.737629 +vt 0.690890 0.728639 +vt 0.681985 0.737629 +vt 0.681985 0.728639 +vt 0.672836 0.737629 +vt 0.672836 0.728639 +vt 0.663617 0.737629 +vt 0.663617 0.728639 +vt 0.654295 0.737629 +vt 0.654295 0.728639 +vt 0.645609 0.737629 +vt 0.645609 0.728639 +vt 0.635841 0.737629 +vt 0.635841 0.728639 +vt 0.737905 0.737629 +vt 0.745988 0.737629 +vt 0.745988 0.728639 +vt 0.737905 0.728639 +vt 0.728889 0.737629 +vt 0.728889 0.728639 +vt 0.719217 0.737629 +vt 0.719217 0.728639 +vt 0.586105 0.759121 +vt 0.595477 0.759121 +vt 0.595477 0.731390 +vt 0.586105 0.731390 +vt 0.577747 0.759121 +vt 0.577747 0.731390 +vt 0.569212 0.759121 +vt 0.569212 0.731390 +vt 0.560443 0.759121 +vt 0.560443 0.731390 +vt 0.551607 0.759121 +vt 0.551607 0.731390 +vt 0.542673 0.759121 +vt 0.542673 0.731390 +vt 0.534348 0.759121 +vt 0.534348 0.731390 +vt 0.524987 0.759121 +vt 0.524987 0.731390 +vt 0.622807 0.759121 +vt 0.630554 0.759121 +vt 0.630554 0.731390 +vt 0.622807 0.731390 +vt 0.614165 0.759121 +vt 0.614165 0.731390 +vt 0.604896 0.759121 +vt 0.604896 0.731390 +vt 0.756599 0.743685 +vt 0.747637 0.743685 +vt 0.747637 0.747687 +vt 0.756599 0.747687 +vt 0.645304 0.743685 +vt 0.634474 0.743685 +vt 0.634474 0.747687 +vt 0.645304 0.747687 +vt 0.654934 0.743685 +vt 0.654934 0.747687 +vt 0.665270 0.743685 +vt 0.665270 0.747687 +vt 0.675491 0.743685 +vt 0.675491 0.747687 +vt 0.685636 0.743685 +vt 0.685636 0.747687 +vt 0.695509 0.743685 +vt 0.695509 0.747687 +vt 0.705178 0.743685 +vt 0.705178 0.747687 +vt 0.716021 0.743685 +vt 0.716021 0.747687 +vt 0.726917 0.743685 +vt 0.726917 0.747687 +vt 0.737640 0.743685 +vt 0.737640 0.747687 +vt 0.699611 0.740018 +vt 0.709390 0.740018 +vt 0.709390 0.737629 +vt 0.699611 0.737629 +vt 0.690890 0.740018 +vt 0.690890 0.737629 +vt 0.681985 0.740018 +vt 0.681985 0.737629 +vt 0.672836 0.740018 +vt 0.672836 0.737629 +vt 0.663617 0.740018 +vt 0.663617 0.737629 +vt 0.654295 0.740018 +vt 0.654295 0.737629 +vt 0.645609 0.740018 +vt 0.645609 0.737629 +vt 0.635841 0.740018 +vt 0.635841 0.737629 +vt 0.737905 0.740018 +vt 0.745988 0.740018 +vt 0.745988 0.737629 +vt 0.737905 0.737629 +vt 0.728889 0.740018 +vt 0.728889 0.737629 +vt 0.719217 0.740018 +vt 0.719217 0.737629 +vt 0.737905 0.728639 +vt 0.745988 0.728639 +vt 0.745988 0.723912 +vt 0.737905 0.723912 +vt 0.635841 0.728639 +vt 0.645609 0.728639 +vt 0.645609 0.723912 +vt 0.635841 0.723912 +vt 0.654295 0.728639 +vt 0.654295 0.723912 +vt 0.663617 0.728639 +vt 0.663617 0.723912 +vt 0.672836 0.728639 +vt 0.672836 0.723912 +vt 0.681985 0.728639 +vt 0.681985 0.723912 +vt 0.690890 0.728639 +vt 0.690890 0.723912 +vt 0.699611 0.728639 +vt 0.699611 0.723912 +vt 0.709390 0.728639 +vt 0.709390 0.723912 +vt 0.719217 0.728639 +vt 0.719217 0.723912 +vt 0.728889 0.728639 +vt 0.728889 0.723912 +vt 0.972643 0.711952 +vt 0.585126 0.660273 +vt 0.632629 0.636445 +vt 0.632629 0.628080 +vt 0.585126 0.628079 +vt 0.585126 0.636445 +vt 0.901080 0.287611 +vt 0.968933 0.541315 +vt 0.968933 0.602040 +vt 0.990302 0.602040 +vt 0.990302 0.541315 +vt 0.832599 0.220025 +vt 0.832599 0.242946 +vt 0.838109 0.242946 +vt 0.838109 0.220025 +vt 0.743574 0.004685 +vt 0.983460 0.088298 +vt 0.874107 0.088298 +vt 0.874107 0.147522 +vt 0.883113 0.147522 +vt 0.883113 0.088298 +vt 0.887681 0.834634 +vt 0.872827 0.839011 +vt 0.877978 0.856483 +vt 0.889929 0.851614 +vt 0.890365 0.983221 +vt 0.878460 0.981310 +vt 0.877263 0.993981 +vt 0.889869 0.995168 +vt 0.862885 0.858991 +vt 0.868188 0.873530 +vt 0.877232 0.872658 +vt 0.877978 0.856483 +vt 0.914042 0.872083 +vt 0.920439 0.856190 +vt 0.905096 0.852899 +vt 0.905011 0.870715 +vt 0.914115 0.980316 +vt 0.911058 0.972195 +vt 0.902126 0.972930 +vt 0.901777 0.982274 +vt 0.877310 0.972490 +vt 0.868396 0.971865 +vt 0.865750 0.979998 +vt 0.878460 0.981310 +vt 0.905493 0.835033 +vt 0.905096 0.852899 +vt 0.901777 0.982274 +vt 0.902012 0.994943 +vt 0.875440 0.921242 +vt 0.903952 0.919621 +vt 0.913021 0.919889 +vt 0.903952 0.919621 +vt 0.866390 0.921260 +vt 0.875440 0.921242 +vt 0.699648 0.948281 +vt 0.703024 0.963093 +vt 0.722927 0.960191 +vt 0.723741 0.946973 +vt 0.843442 0.947405 +vt 0.845428 0.959954 +vt 0.862856 0.959286 +vt 0.861806 0.945918 +vt 0.730020 0.993369 +vt 0.738191 0.987277 +vt 0.736322 0.974965 +vt 0.725525 0.973947 +vt 0.738020 0.932516 +vt 0.729094 0.925627 +vt 0.723741 0.946973 +vt 0.736016 0.945534 +vt 0.844607 0.974824 +vt 0.862882 0.974810 +vt 0.705509 0.977442 +vt 0.725525 0.973947 +vt 0.778391 0.975353 +vt 0.779001 0.945164 +vt 0.779225 0.932086 +vt 0.779001 0.945164 +vt 0.778187 0.988191 +vt 0.778391 0.975353 +vt 0.840872 0.929298 +vt 0.827900 0.934040 +vt 0.828994 0.946923 +vt 0.843442 0.947405 +vt 0.831004 0.976284 +vt 0.829627 0.988919 +vt 0.842546 0.993311 +vt 0.844607 0.974824 +vt 0.955654 0.852027 +vt 0.968477 0.850066 +vt 0.964984 0.833800 +vt 0.951820 0.836633 +vt 0.990713 0.968848 +vt 0.984178 0.962364 +vt 0.972286 0.964119 +vt 0.970981 0.973276 +vt 0.932031 0.956549 +vt 0.925678 0.966767 +vt 0.945665 0.969435 +vt 0.943732 0.957997 +vt 0.928983 0.858811 +vt 0.933210 0.865106 +vt 0.944710 0.863614 +vt 0.945065 0.855897 +vt 0.970146 0.858760 +vt 0.981805 0.859541 +vt 0.985608 0.851314 +vt 0.968477 0.850066 +vt 0.938975 0.992992 +vt 0.952457 0.996821 +vt 0.955902 0.974128 +vt 0.945065 0.855897 +vt 0.940448 0.839911 +vt 0.932698 0.907687 +vt 0.944597 0.907822 +vt 0.983276 0.912869 +vt 0.971340 0.913129 +vt 0.971340 0.913129 +vt 0.944597 0.907822 +vt 0.263387 0.820968 +vt 0.260536 0.830772 +vt 0.256313 0.814849 +vt 0.246400 0.818415 +vt 0.243450 0.828163 +vt 0.250511 0.834400 +vt 0.250511 0.834400 +vt 0.263341 0.817739 +vt 0.260016 0.829144 +vt 0.255109 0.810639 +vt 0.263341 0.817739 +vt 0.243571 0.814783 +vt 0.255109 0.810639 +vt 0.240112 0.826113 +vt 0.243571 0.814783 +vt 0.248325 0.833374 +vt 0.240112 0.826113 +vt 0.260016 0.829144 +vt 0.248325 0.833374 +vt 0.260162 0.815582 +vt 0.257240 0.825524 +vt 0.252981 0.809384 +vt 0.242889 0.813007 +vt 0.252981 0.809384 +vt 0.239864 0.822891 +vt 0.247031 0.829211 +vt 0.273779 0.846308 +vt 0.268600 0.851651 +vt 0.275811 0.837484 +vt 0.272655 0.834114 +vt 0.267535 0.839468 +vt 0.265509 0.848181 +vt 0.265509 0.848181 +vt 0.270482 0.845127 +vt 0.264499 0.851328 +vt 0.272847 0.834896 +vt 0.270482 0.845127 +vt 0.269220 0.831016 +vt 0.272847 0.834896 +vt 0.263314 0.837231 +vt 0.269220 0.831016 +vt 0.260958 0.847313 +vt 0.263314 0.837231 +vt 0.264499 0.851328 +vt 0.260958 0.847313 +vt 0.266182 0.842857 +vt 0.261006 0.848258 +vt 0.268246 0.833980 +vt 0.265129 0.830614 +vt 0.268246 0.833980 +vt 0.260012 0.836023 +vt 0.257954 0.844789 +vt 0.610201 0.993026 +vt 0.622646 0.997530 +vt 0.623014 0.991721 +vt 0.613835 0.988165 +vt 0.635725 0.994072 +vt 0.632533 0.989195 +vt 0.644223 0.983536 +vt 0.638750 0.981532 +vt 0.644854 0.970053 +vt 0.639268 0.971669 +vt 0.637480 0.958808 +vt 0.633890 0.963368 +vt 0.624946 0.954015 +vt 0.624658 0.959808 +vt 0.611984 0.957453 +vt 0.615092 0.962349 +vt 0.603448 0.967843 +vt 0.608834 0.970042 +vt 0.602536 0.981548 +vt 0.608284 0.979956 +vt 0.638750 0.981532 +vt 0.223917 0.932574 +vt 0.232999 0.932574 +vt 0.233000 0.920753 +vt 0.223917 0.920753 +vt 0.242220 0.932574 +vt 0.242221 0.920753 +vt 0.251160 0.932574 +vt 0.251160 0.920753 +vt 0.260035 0.932574 +vt 0.260035 0.920753 +vt 0.268544 0.932574 +vt 0.268544 0.920753 +vt 0.277163 0.932574 +vt 0.277163 0.920753 +vt 0.207906 0.932574 +vt 0.215683 0.932574 +vt 0.215684 0.920753 +vt 0.207906 0.920753 +vt 0.515161 0.920274 +vt 0.508480 0.920274 +vt 0.513159 0.935389 +vt 0.482685 0.920274 +vt 0.482685 0.935389 +vt 0.277163 0.910653 +vt 0.268544 0.910653 +vt 0.260035 0.910653 +vt 0.251161 0.910653 +vt 0.242220 0.910653 +vt 0.233000 0.910653 +vt 0.223918 0.910653 +vt 0.215683 0.910653 +vt 0.207906 0.910653 +vt 0.843661 0.831557 +vt 0.819500 0.827674 +vt 0.819713 0.842626 +vt 0.843172 0.844352 +vt 0.843172 0.844352 +vt 0.819713 0.842626 +vt 0.821588 0.854563 +vt 0.841518 0.857027 +vt 0.841518 0.857027 +vt 0.821588 0.854563 +vt 0.820635 0.869631 +vt 0.838933 0.868515 +vt 0.743085 0.844945 +vt 0.745467 0.860837 +vt 0.760808 0.858542 +vt 0.758528 0.842570 +vt 0.783801 0.855202 +vt 0.760808 0.858542 +vt 0.763696 0.873734 +vt 0.786416 0.871830 +vt 0.782820 0.842597 +vt 0.783786 0.825691 +vt 0.759320 0.826753 +vt 0.758528 0.842570 +vt 0.783801 0.855202 +vt 0.782820 0.842597 +vt 0.758528 0.842570 +vt 0.760808 0.858542 +vt 0.802566 0.854047 +vt 0.803938 0.869997 +vt 0.801575 0.826974 +vt 0.800737 0.842863 +vt 0.800737 0.842863 +vt 0.802566 0.854047 +vt 0.542787 0.946218 +vt 0.536571 0.965426 +vt 0.545739 0.965419 +vt 0.550219 0.951608 +vt 0.542788 0.984598 +vt 0.550215 0.979216 +vt 0.559123 0.996488 +vt 0.561961 0.987757 +vt 0.579316 0.996466 +vt 0.576476 0.987751 +vt 0.595626 0.984618 +vt 0.588215 0.979224 +vt 0.601901 0.965425 +vt 0.592711 0.965417 +vt 0.595647 0.946198 +vt 0.588222 0.951603 +vt 0.579308 0.934361 +vt 0.576475 0.943077 +vt 0.559139 0.934348 +vt 0.561967 0.943074 +vt 0.797692 0.676526 +vt 0.772294 0.670822 +vt 0.772294 0.670822 +vt 0.795301 0.685331 +vt 0.772017 0.680774 +vt 0.542787 0.946218 +vt 0.536571 0.965426 +vt 0.545739 0.965419 +vt 0.550219 0.951608 +vt 0.542788 0.984598 +vt 0.550215 0.979216 +vt 0.559123 0.996488 +vt 0.561961 0.987757 +vt 0.579316 0.996466 +vt 0.576476 0.987751 +vt 0.595626 0.984618 +vt 0.588215 0.979224 +vt 0.601901 0.965425 +vt 0.592711 0.965417 +vt 0.595647 0.946198 +vt 0.588222 0.951603 +vt 0.579308 0.934361 +vt 0.576475 0.943077 +vt 0.559139 0.934348 +vt 0.561967 0.943074 +vt 0.795301 0.685331 +vt 0.792431 0.659122 +vt 0.843661 0.831557 +vt 0.819500 0.827674 +vt 0.819713 0.842626 +vt 0.843172 0.844352 +vt 0.843172 0.844352 +vt 0.819713 0.842626 +vt 0.821588 0.854563 +vt 0.841518 0.857027 +vt 0.841518 0.857027 +vt 0.821588 0.854563 +vt 0.820635 0.869631 +vt 0.838933 0.868515 +vt 0.783801 0.855202 +vt 0.760808 0.858542 +vt 0.763696 0.873734 +vt 0.786416 0.871830 +vt 0.782819 0.842597 +vt 0.783786 0.825691 +vt 0.759320 0.826753 +vt 0.758528 0.842570 +vt 0.783801 0.855202 +vt 0.782819 0.842597 +vt 0.758528 0.842570 +vt 0.760808 0.858542 +vt 0.802566 0.854047 +vt 0.803938 0.869997 +vt 0.801575 0.826974 +vt 0.800737 0.842863 +vt 0.800737 0.842863 +vt 0.802566 0.854047 +vt 0.224206 0.989148 +vt 0.238950 0.989148 +vt 0.238950 0.961720 +vt 0.224206 0.961720 +vt 0.255949 0.989148 +vt 0.255949 0.961720 +vt 0.272788 0.989148 +vt 0.272788 0.961720 +vt 0.288974 0.989148 +vt 0.288974 0.961720 +vt 0.306463 0.989148 +vt 0.306463 0.961720 +vt 0.323231 0.989148 +vt 0.323231 0.961720 +vt 0.339239 0.989148 +vt 0.339239 0.961720 +vt 0.355049 0.989148 +vt 0.355049 0.961720 +vt 0.368223 0.989148 +vt 0.368223 0.961720 +vt 0.207121 0.989148 +vt 0.207121 0.961720 +vt 0.207395 0.958377 +vt 0.221277 0.958377 +vt 0.221277 0.940272 +vt 0.207395 0.940272 +vt 0.236980 0.958377 +vt 0.236980 0.940272 +vt 0.252534 0.958377 +vt 0.252534 0.940272 +vt 0.267492 0.958377 +vt 0.267492 0.940272 +vt 0.282960 0.958377 +vt 0.282960 0.940272 +vt 0.297506 0.958377 +vt 0.297506 0.940272 +vt 0.312107 0.958377 +vt 0.312107 0.940272 +vt 0.326609 0.958377 +vt 0.326609 0.940272 +vt 0.340477 0.958377 +vt 0.340477 0.940272 +vt 0.353511 0.958377 +vt 0.353511 0.940272 +vt 0.244109 0.761706 +vt 0.254564 0.761442 +vt 0.254755 0.752994 +vt 0.243283 0.752660 +vt 0.265432 0.762039 +vt 0.265848 0.753583 +vt 0.276282 0.763408 +vt 0.277002 0.753908 +vt 0.286803 0.765604 +vt 0.288526 0.754549 +vt 0.296804 0.768765 +vt 0.300125 0.756467 +vt 0.286803 0.765604 +vt 0.288526 0.754549 +vt 0.276282 0.763408 +vt 0.277002 0.753908 +vt 0.265432 0.762039 +vt 0.265848 0.753583 +vt 0.254564 0.761442 +vt 0.254755 0.752994 +vt 0.246936 0.782374 +vt 0.254690 0.781015 +vt 0.262737 0.781821 +vt 0.270448 0.784814 +vt 0.277369 0.789342 +vt 0.283527 0.794494 +vt 0.277369 0.789342 +vt 0.270448 0.784814 +vt 0.262737 0.781821 +vt 0.254690 0.781015 +vt 0.248280 0.806800 +vt 0.253085 0.805996 +vt 0.257812 0.806614 +vt 0.262222 0.808685 +vt 0.266210 0.811780 +vt 0.269832 0.815302 +vt 0.266210 0.811780 +vt 0.262222 0.808685 +vt 0.257812 0.806614 +vt 0.253085 0.805996 +vt 0.795787 0.887974 +vt 0.795787 0.887974 +vt 0.792508 0.888345 +vt 0.799010 0.924296 +vt 0.814208 0.917624 +vt 0.792508 0.888345 +vt 0.789242 0.887569 +vt 0.782798 0.925228 +vt 0.799010 0.924296 +vt 0.786081 0.886458 +vt 0.783049 0.885021 +vt 0.755406 0.909143 +vt 0.767554 0.919929 +vt 0.783049 0.885021 +vt 0.780759 0.882646 +vt 0.747574 0.894536 +vt 0.755406 0.909143 +vt 0.224206 0.989148 +vt 0.238950 0.989148 +vt 0.238950 0.961720 +vt 0.224206 0.961720 +vt 0.255949 0.989148 +vt 0.255949 0.961720 +vt 0.272788 0.989148 +vt 0.272788 0.961720 +vt 0.288974 0.989148 +vt 0.288974 0.961720 +vt 0.306463 0.989148 +vt 0.306463 0.961720 +vt 0.323231 0.989148 +vt 0.323231 0.961720 +vt 0.339239 0.989148 +vt 0.339239 0.961720 +vt 0.355049 0.989148 +vt 0.355049 0.961720 +vt 0.368223 0.989148 +vt 0.368223 0.961720 +vt 0.207121 0.989148 +vt 0.207121 0.961720 +vt 0.421130 0.976369 +vt 0.207395 0.958377 +vt 0.221277 0.958377 +vt 0.221277 0.940272 +vt 0.207395 0.940272 +vt 0.236980 0.958377 +vt 0.236980 0.940272 +vt 0.252534 0.958377 +vt 0.252534 0.940272 +vt 0.267492 0.958377 +vt 0.267492 0.940272 +vt 0.282960 0.958377 +vt 0.282960 0.940272 +vt 0.297506 0.958377 +vt 0.297506 0.940272 +vt 0.312107 0.958377 +vt 0.312107 0.940272 +vt 0.326609 0.958377 +vt 0.326609 0.940272 +vt 0.340477 0.958377 +vt 0.340477 0.940272 +vt 0.353511 0.958377 +vt 0.353511 0.940272 +vt 0.244109 0.761706 +vt 0.254564 0.761442 +vt 0.254755 0.752994 +vt 0.243283 0.752660 +vt 0.265432 0.762039 +vt 0.265848 0.753583 +vt 0.276282 0.763408 +vt 0.277002 0.753908 +vt 0.286803 0.765604 +vt 0.288526 0.754549 +vt 0.296804 0.768765 +vt 0.300125 0.756467 +vt 0.286803 0.765604 +vt 0.288526 0.754549 +vt 0.276282 0.763408 +vt 0.277002 0.753908 +vt 0.265432 0.762039 +vt 0.265848 0.753583 +vt 0.254564 0.761442 +vt 0.254755 0.752994 +vt 0.246936 0.782374 +vt 0.254690 0.781015 +vt 0.262737 0.781821 +vt 0.270448 0.784814 +vt 0.277369 0.789342 +vt 0.283527 0.794494 +vt 0.277369 0.789342 +vt 0.270448 0.784814 +vt 0.262737 0.781821 +vt 0.254690 0.781015 +vt 0.248280 0.806800 +vt 0.253085 0.805996 +vt 0.257812 0.806614 +vt 0.262222 0.808685 +vt 0.266210 0.811780 +vt 0.269832 0.815302 +vt 0.266210 0.811780 +vt 0.262222 0.808685 +vt 0.257812 0.806614 +vt 0.253085 0.805996 +vt 0.795787 0.887974 +vt 0.792508 0.888345 +vt 0.799010 0.924296 +vt 0.814208 0.917624 +vt 0.792508 0.888345 +vt 0.789242 0.887569 +vt 0.782798 0.925228 +vt 0.799010 0.924296 +vt 0.786081 0.886458 +vt 0.783049 0.885021 +vt 0.755406 0.909143 +vt 0.767554 0.919929 +vt 0.783049 0.885021 +vt 0.780759 0.882646 +vt 0.747574 0.894536 +vt 0.755406 0.909143 +vt 0.174377 0.138546 +vt 0.207891 0.172147 +vt 0.207779 0.158104 +vt 0.181977 0.172356 +vt 0.433241 0.397430 +vt 0.456386 0.397430 +vt 0.456386 0.347763 +vt 0.433241 0.347763 +vt 0.325524 0.397430 +vt 0.351838 0.397430 +vt 0.351838 0.347763 +vt 0.325524 0.347763 +vt 0.371053 0.397430 +vt 0.371053 0.347763 +vt 0.390364 0.397430 +vt 0.390364 0.347763 +vt 0.412002 0.397430 +vt 0.412002 0.347763 +vt 0.433587 0.254843 +vt 0.445877 0.254833 +vt 0.445709 0.192432 +vt 0.434165 0.192550 +vt 0.422955 0.254845 +vt 0.424181 0.192806 +vt 0.410655 0.254588 +vt 0.412743 0.193408 +vt 0.390056 0.254089 +vt 0.391217 0.194966 +vt 0.458216 0.255010 +vt 0.468927 0.254922 +vt 0.467250 0.192560 +vt 0.457255 0.192394 +vt 0.456386 0.347763 +vt 0.444716 0.005849 +vt 0.427037 0.006031 +vt 0.411726 0.006585 +vt 0.394088 0.008055 +vt 0.371787 0.010630 +vt 0.478706 0.193054 +vt 0.500278 0.194393 +vt 0.517699 0.009834 +vt 0.495369 0.007501 +vt 0.477713 0.006223 +vt 0.462396 0.005837 +vt 0.390363 0.397430 +vt 0.390363 0.347763 +vt 0.371053 0.347763 +vt 0.371053 0.397430 +vt 0.505253 0.397430 +vt 0.505253 0.347763 +vt 0.478889 0.347763 +vt 0.478889 0.397430 +vt 0.456386 0.347763 +vt 0.456386 0.397430 +vt 0.433241 0.347763 +vt 0.433241 0.397430 +vt 0.412002 0.347763 +vt 0.412002 0.397430 +vt 0.458216 0.255011 +vt 0.457255 0.192394 +vt 0.445709 0.192432 +vt 0.445877 0.254833 +vt 0.468927 0.254922 +vt 0.467249 0.192560 +vt 0.481304 0.254550 +vt 0.478706 0.193054 +vt 0.502013 0.253817 +vt 0.500278 0.194392 +vt 0.410655 0.254588 +vt 0.412743 0.193408 +vt 0.391217 0.194966 +vt 0.390056 0.254089 +vt 0.422955 0.254845 +vt 0.424181 0.192806 +vt 0.433587 0.254843 +vt 0.434165 0.192550 +vt 0.371053 0.347763 +vt 0.351838 0.334431 +vt 0.351838 0.347763 +vt 0.325523 0.334431 +vt 0.325523 0.347763 +vt 0.505253 0.347763 +vt 0.505253 0.334431 +vt 0.462396 0.005837 +vt 0.444716 0.005849 +vt 0.477713 0.006223 +vt 0.495369 0.007501 +vt 0.517699 0.009835 +vt 0.394088 0.008055 +vt 0.371787 0.010630 +vt 0.411726 0.006585 +vt 0.427037 0.006031 +vt 0.573963 0.010867 +vt 0.517699 0.009835 +vt 0.500278 0.194392 +vt 0.546586 0.196227 +vt 0.546586 0.196228 +vt 0.502013 0.253817 +vt 0.546788 0.255153 +vt 0.559262 0.348354 +vt 0.559262 0.334313 +vt 0.505244 0.334437 +vt 0.505244 0.347769 +vt 0.433234 0.397436 +vt 0.456379 0.397436 +vt 0.456379 0.347769 +vt 0.433234 0.347769 +vt 0.478881 0.397436 +vt 0.478881 0.347769 +vt 0.505244 0.397436 +vt 0.505244 0.347769 +vt 0.325517 0.397436 +vt 0.351830 0.397436 +vt 0.351830 0.347769 +vt 0.325517 0.347769 +vt 0.371046 0.397436 +vt 0.371046 0.347769 +vt 0.390356 0.397436 +vt 0.390356 0.347769 +vt 0.411994 0.397436 +vt 0.411994 0.347769 +vt 0.433579 0.254849 +vt 0.445870 0.254839 +vt 0.445702 0.192438 +vt 0.434159 0.192556 +vt 0.422947 0.254850 +vt 0.424174 0.192811 +vt 0.410648 0.254593 +vt 0.412736 0.193413 +vt 0.390049 0.254094 +vt 0.391210 0.194972 +vt 0.481297 0.254555 +vt 0.502006 0.253823 +vt 0.500270 0.194399 +vt 0.478698 0.193060 +vt 0.468920 0.254927 +vt 0.467242 0.192566 +vt 0.458209 0.255016 +vt 0.457247 0.192400 +vt 0.456379 0.347769 +vt 0.478881 0.334437 +vt 0.478881 0.347769 +vt 0.505244 0.334437 +vt 0.505244 0.347769 +vt 0.444709 0.005856 +vt 0.427029 0.006038 +vt 0.411720 0.006592 +vt 0.394081 0.008062 +vt 0.371780 0.010637 +vt 0.517692 0.009840 +vt 0.495361 0.007508 +vt 0.477706 0.006230 +vt 0.462389 0.005844 +vt 0.573080 0.111794 +vt 0.617867 0.111794 +vt 0.617853 0.089806 +vt 0.573071 0.089806 +vt 0.623897 0.111794 +vt 0.647030 0.111794 +vt 0.647029 0.089806 +vt 0.623887 0.089806 +vt 0.670910 0.111794 +vt 0.670920 0.089806 +vt 0.623897 0.111794 +vt 0.623887 0.089806 +vt 0.616463 0.083451 +vt 0.573144 0.083451 +vt 0.624826 0.083451 +vt 0.647085 0.083451 +vt 0.624826 0.083451 +vt 0.669990 0.083451 +vt 0.573159 0.118205 +vt 0.616489 0.118205 +vt 0.647086 0.118205 +vt 0.669977 0.118205 +vt 0.624841 0.118205 +vt 0.624841 0.118205 +vt 0.573080 0.111794 +vt 0.617867 0.111794 +vt 0.617853 0.089806 +vt 0.573071 0.089806 +vt 0.721801 0.089806 +vt 0.678175 0.089806 +vt 0.678162 0.111794 +vt 0.721792 0.111794 +vt 0.623897 0.111794 +vt 0.647030 0.111794 +vt 0.647029 0.089806 +vt 0.623887 0.089806 +vt 0.670910 0.111794 +vt 0.670920 0.089806 +vt 0.670920 0.089806 +vt 0.670910 0.111794 +vt 0.623897 0.111794 +vt 0.623887 0.089806 +vt 0.616463 0.083451 +vt 0.573144 0.083451 +vt 0.721708 0.083451 +vt 0.679462 0.083451 +vt 0.624826 0.083451 +vt 0.647085 0.083451 +vt 0.624826 0.083451 +vt 0.669990 0.083451 +vt 0.669990 0.083451 +vt 0.573159 0.118205 +vt 0.616489 0.118205 +vt 0.679434 0.118205 +vt 0.721693 0.118205 +vt 0.669977 0.118205 +vt 0.647086 0.118205 +vt 0.669977 0.118205 +vt 0.624841 0.118205 +vt 0.624841 0.118205 +vt 0.715725 0.056066 +vt 0.715723 0.032352 +vt 0.682043 0.032352 +vt 0.682041 0.056066 +vt 0.715726 0.078252 +vt 0.682040 0.078251 +vt 0.710043 0.029956 +vt 0.687722 0.029957 +vt 0.687718 0.080599 +vt 0.710048 0.080599 +vt 0.573080 0.111794 +vt 0.573071 0.089806 +vt 0.617853 0.089806 +vt 0.617867 0.111794 +vt 0.623897 0.111794 +vt 0.623887 0.089806 +vt 0.647029 0.089806 +vt 0.647030 0.111794 +vt 0.670920 0.089806 +vt 0.670910 0.111794 +vt 0.623887 0.089806 +vt 0.623897 0.111794 +vt 0.573144 0.083451 +vt 0.616463 0.083451 +vt 0.624826 0.083451 +vt 0.624826 0.083451 +vt 0.647085 0.083451 +vt 0.669990 0.083451 +vt 0.616489 0.118205 +vt 0.573159 0.118205 +vt 0.669977 0.118205 +vt 0.647086 0.118205 +vt 0.624841 0.118205 +vt 0.624841 0.118205 +vt 0.715724 0.056066 +vt 0.682041 0.056066 +vt 0.682043 0.032353 +vt 0.715722 0.032353 +vt 0.715725 0.078251 +vt 0.682040 0.078251 +vt 0.710043 0.029957 +vt 0.687723 0.029957 +vt 0.710048 0.080599 +vt 0.687718 0.080599 +vt 0.213034 0.197378 +vt 0.202559 0.197150 +vt 0.202544 0.207042 +vt 0.213019 0.206849 +vt 0.186358 0.196200 +vt 0.186344 0.207939 +vt 0.180225 0.157496 +vt 0.168351 0.158737 +vt 0.169209 0.175931 +vt 0.179621 0.174454 +vt 0.181754 0.189784 +vt 0.187389 0.183595 +vt 0.203120 0.194920 +vt 0.202461 0.187693 +vt 0.180231 0.157515 +vt 0.179629 0.174456 +vt 0.169227 0.175931 +vt 0.168368 0.158755 +vt 0.187388 0.183587 +vt 0.181758 0.189769 +vt 0.202444 0.187681 +vt 0.203102 0.194901 +vt 0.144716 0.126890 +vt 0.143601 0.143303 +vt 0.153187 0.143957 +vt 0.155341 0.127980 +vt 0.166833 0.147117 +vt 0.167453 0.130088 +vt 0.156468 0.130088 +vt 0.157086 0.147117 +vt 0.143677 0.162180 +vt 0.151780 0.162337 +vt 0.166138 0.159930 +vt 0.157899 0.159930 +vt 0.145001 0.185061 +vt 0.150461 0.185078 +vt 0.164736 0.176598 +vt 0.157603 0.176598 +vt 0.028546 0.972752 +vt 0.085950 0.973428 +vt 0.086440 0.919348 +vt 0.029073 0.919083 +vt 0.166909 0.908600 +vt 0.166909 0.944868 +vt 0.201591 0.944868 +vt 0.201591 0.908600 +vt 0.144381 0.973663 +vt 0.144866 0.920019 +vt 0.166909 0.981830 +vt 0.201591 0.981830 +vt 0.213034 0.197378 +vt 0.202559 0.197150 +vt 0.202544 0.207042 +vt 0.213019 0.206849 +vt 0.186358 0.196200 +vt 0.186344 0.207939 +vt 0.180225 0.157496 +vt 0.168351 0.158737 +vt 0.169209 0.175931 +vt 0.179621 0.174454 +vt 0.181754 0.189784 +vt 0.187389 0.183595 +vt 0.203120 0.194920 +vt 0.202461 0.187693 +vt 0.180231 0.157515 +vt 0.179629 0.174456 +vt 0.169227 0.175931 +vt 0.168368 0.158754 +vt 0.187388 0.183587 +vt 0.181758 0.189769 +vt 0.202444 0.187681 +vt 0.203102 0.194901 +vt 0.144716 0.126890 +vt 0.143601 0.143303 +vt 0.153187 0.143957 +vt 0.155341 0.127980 +vt 0.166833 0.147117 +vt 0.167453 0.130088 +vt 0.156468 0.130088 +vt 0.157086 0.147117 +vt 0.143677 0.162180 +vt 0.151778 0.162337 +vt 0.166138 0.159930 +vt 0.157900 0.159930 +vt 0.145001 0.185061 +vt 0.150461 0.185078 +vt 0.164736 0.176598 +vt 0.157603 0.176598 +vt 0.222181 0.184952 +vt 0.222181 0.184952 +vt 0.213034 0.197378 +vt 0.202559 0.197150 +vt 0.202544 0.207042 +vt 0.213019 0.206849 +vt 0.186358 0.196200 +vt 0.186344 0.207939 +vt 0.180231 0.157515 +vt 0.168368 0.158755 +vt 0.169227 0.175931 +vt 0.179629 0.174456 +vt 0.181758 0.189769 +vt 0.187388 0.183587 +vt 0.203102 0.194901 +vt 0.202444 0.187681 +vt 0.180231 0.157515 +vt 0.179629 0.174456 +vt 0.169227 0.175931 +vt 0.168368 0.158755 +vt 0.187388 0.183587 +vt 0.181758 0.189769 +vt 0.202444 0.187681 +vt 0.203102 0.194901 +vt 0.144716 0.126890 +vt 0.143601 0.143303 +vt 0.153187 0.143957 +vt 0.155341 0.127980 +vt 0.166833 0.147117 +vt 0.167453 0.130088 +vt 0.156468 0.130088 +vt 0.157086 0.147117 +vt 0.143677 0.162180 +vt 0.151778 0.162337 +vt 0.166138 0.159930 +vt 0.157900 0.159930 +vt 0.145001 0.185061 +vt 0.150461 0.185078 +vt 0.164736 0.176598 +vt 0.157603 0.176598 +vt 0.028546 0.972752 +vt 0.085950 0.973428 +vt 0.086440 0.919348 +vt 0.029073 0.919083 +vt 0.166909 0.908600 +vt 0.166909 0.944868 +vt 0.201591 0.944868 +vt 0.201591 0.908600 +vt 0.144381 0.973663 +vt 0.144866 0.920019 +vt 0.166909 0.981830 +vt 0.201591 0.981830 +vt 0.222181 0.184952 +vt 0.222181 0.184952 +vt 0.213034 0.197378 +vt 0.202559 0.197150 +vt 0.202544 0.207042 +vt 0.213019 0.206849 +vt 0.223974 0.196470 +vt 0.213034 0.197378 +vt 0.213019 0.206849 +vt 0.223956 0.207798 +vt 0.186358 0.196200 +vt 0.186344 0.207939 +vt 0.180231 0.157515 +vt 0.168368 0.158755 +vt 0.169227 0.175931 +vt 0.179629 0.174456 +vt 0.181758 0.189769 +vt 0.187388 0.183587 +vt 0.203102 0.194901 +vt 0.202444 0.187681 +vt 0.180231 0.157515 +vt 0.179629 0.174456 +vt 0.169227 0.175931 +vt 0.168368 0.158755 +vt 0.187388 0.183587 +vt 0.181758 0.189769 +vt 0.202444 0.187681 +vt 0.203102 0.194901 +vt 0.144716 0.126890 +vt 0.143601 0.143303 +vt 0.153187 0.143957 +vt 0.155341 0.127980 +vt 0.166833 0.147117 +vt 0.167453 0.130088 +vt 0.156468 0.130088 +vt 0.157086 0.147117 +vt 0.143677 0.162180 +vt 0.151778 0.162337 +vt 0.166138 0.159930 +vt 0.157900 0.159930 +vt 0.145001 0.185061 +vt 0.150461 0.185078 +vt 0.164736 0.176598 +vt 0.157603 0.176598 +vt 0.890755 0.733858 +vt 0.898922 0.733858 +vt 0.898869 0.702745 +vt 0.891741 0.702745 +vt 0.907283 0.733858 +vt 0.906193 0.702745 +vt 0.915853 0.733858 +vt 0.913663 0.702745 +vt 0.924218 0.733858 +vt 0.920871 0.702745 +vt 0.932056 0.733858 +vt 0.927442 0.702745 +vt 0.849772 0.733858 +vt 0.857324 0.733858 +vt 0.862545 0.702745 +vt 0.856201 0.702745 +vt 0.865355 0.733858 +vt 0.869515 0.702745 +vt 0.873860 0.733858 +vt 0.876967 0.702745 +vt 0.882431 0.733858 +vt 0.884482 0.702745 +vt 0.877212 0.695801 +vt 0.869967 0.695801 +vt 0.863093 0.695801 +vt 0.855827 0.695801 +vt 0.927610 0.695801 +vt 0.919918 0.695801 +vt 0.912946 0.695801 +vt 0.906382 0.695801 +vt 0.899676 0.695801 +vt 0.892485 0.695801 +vt 0.884848 0.695801 +vt 0.890755 0.733859 +vt 0.898921 0.733859 +vt 0.898869 0.702746 +vt 0.891741 0.702746 +vt 0.907283 0.733859 +vt 0.906193 0.702746 +vt 0.915852 0.733859 +vt 0.913663 0.702746 +vt 0.924219 0.733859 +vt 0.920872 0.702746 +vt 0.932056 0.733859 +vt 0.927442 0.702746 +vt 0.849772 0.733859 +vt 0.857325 0.733859 +vt 0.862545 0.702746 +vt 0.856201 0.702746 +vt 0.865356 0.733859 +vt 0.869515 0.702746 +vt 0.873860 0.733859 +vt 0.876967 0.702746 +vt 0.882430 0.733859 +vt 0.884482 0.702746 +vt 0.877212 0.695802 +vt 0.869967 0.695802 +vt 0.863093 0.695802 +vt 0.855827 0.695802 +vt 0.927610 0.695802 +vt 0.919918 0.695802 +vt 0.912946 0.695802 +vt 0.906382 0.695802 +vt 0.899675 0.695802 +vt 0.892485 0.695802 +vt 0.884849 0.695802 +vt 0.447248 0.915094 +vt 0.532627 0.915094 +vt 0.533533 0.849959 +vt 0.448154 0.849959 +vt 0.447248 0.915094 +vt 0.692740 0.609015 +vt 0.717206 0.609015 +vt 0.717206 0.593044 +vt 0.692740 0.593044 +vt 0.742813 0.609015 +vt 0.742813 0.593044 +vt 0.765585 0.609015 +vt 0.765585 0.593044 +vt 0.790694 0.609015 +vt 0.790694 0.593044 +vt 0.814694 0.609015 +vt 0.814694 0.593044 +vt 0.842015 0.609015 +vt 0.842015 0.593044 +vt 0.867997 0.609015 +vt 0.867997 0.593044 +vt 0.893928 0.609015 +vt 0.893928 0.593044 +vt 0.918320 0.609015 +vt 0.918320 0.593044 +vt 0.939929 0.609015 +vt 0.939929 0.593044 +vt 0.692740 0.557075 +vt 0.717206 0.557075 +vt 0.717206 0.549366 +vt 0.692740 0.549445 +vt 0.742813 0.557075 +vt 0.742813 0.549430 +vt 0.765585 0.557075 +vt 0.765585 0.549427 +vt 0.790694 0.557075 +vt 0.790694 0.549449 +vt 0.814694 0.557075 +vt 0.814694 0.549420 +vt 0.842015 0.557075 +vt 0.842015 0.549403 +vt 0.867997 0.557075 +vt 0.867997 0.549417 +vt 0.893928 0.557075 +vt 0.893928 0.549408 +vt 0.918320 0.557075 +vt 0.918320 0.549438 +vt 0.939929 0.557075 +vt 0.939929 0.549388 +vt 0.692740 0.549445 +vt 0.717206 0.549366 +vt 0.717206 0.539500 +vt 0.692740 0.539500 +vt 0.742813 0.549430 +vt 0.742813 0.539500 +vt 0.765585 0.549427 +vt 0.765585 0.539500 +vt 0.790694 0.549449 +vt 0.790694 0.539500 +vt 0.790694 0.549449 +vt 0.814694 0.549420 +vt 0.814694 0.539500 +vt 0.790694 0.539500 +vt 0.842015 0.549403 +vt 0.842015 0.539500 +vt 0.867997 0.549417 +vt 0.867997 0.539500 +vt 0.893928 0.549408 +vt 0.893928 0.539500 +vt 0.918320 0.549438 +vt 0.918320 0.539500 +vt 0.939929 0.549388 +vt 0.939929 0.539500 +vt 0.867997 0.575341 +vt 0.893928 0.575341 +vt 0.893928 0.557075 +vt 0.867997 0.557075 +vt 0.842015 0.575341 +vt 0.842015 0.557075 +vt 0.814694 0.575341 +vt 0.814694 0.557075 +vt 0.790694 0.575341 +vt 0.790694 0.557075 +vt 0.765585 0.575341 +vt 0.765585 0.557075 +vt 0.742813 0.575341 +vt 0.742813 0.557075 +vt 0.717206 0.575341 +vt 0.717206 0.557075 +vt 0.692740 0.575341 +vt 0.692740 0.557075 +vt 0.918320 0.575341 +vt 0.939929 0.575341 +vt 0.939929 0.557075 +vt 0.918320 0.557075 +vt 0.692520 0.407616 +vt 0.692520 0.438477 +vt 0.725542 0.438477 +vt 0.725542 0.407616 +vt 0.725542 0.502057 +vt 0.725542 0.470710 +vt 0.692520 0.470710 +vt 0.692520 0.502057 +vt 0.692520 0.438477 +vt 0.692520 0.453555 +vt 0.725542 0.453555 +vt 0.725542 0.438477 +vt 0.692520 0.518117 +vt 0.692520 0.533770 +vt 0.725542 0.533770 +vt 0.725542 0.518117 +vt 0.729861 0.438477 +vt 0.729861 0.407616 +vt 0.729861 0.453555 +vt 0.729861 0.438477 +vt 0.729861 0.502057 +vt 0.729861 0.470710 +vt 0.729861 0.533770 +vt 0.729861 0.518117 +vt 0.725542 0.502057 +vt 0.729861 0.502057 +vt 0.692520 0.502057 +vt 0.692520 0.470710 +vt 0.725542 0.470710 +vt 0.729861 0.470710 +vt 0.939885 0.344344 +vt 0.939386 0.324138 +vt 0.899998 0.325679 +vt 0.900142 0.343649 +vt 0.939386 0.324138 +vt 0.941715 0.371086 +vt 0.868319 0.324892 +vt 0.868660 0.345654 +vt 0.844879 0.325713 +vt 0.845249 0.345712 +vt 0.939885 0.344344 +vt 0.939386 0.324138 +vt 0.899998 0.325679 +vt 0.900142 0.343649 +vt 0.940809 0.297498 +vt 0.939386 0.324138 +vt 0.900832 0.373884 +vt 0.941715 0.371086 +vt 0.939885 0.344344 +vt 0.868319 0.324892 +vt 0.868660 0.345654 +vt 0.844879 0.325713 +vt 0.845249 0.345712 +vt 0.692520 0.407616 +vt 0.692520 0.438477 +vt 0.725542 0.438477 +vt 0.725542 0.407616 +vt 0.725542 0.502057 +vt 0.725542 0.470710 +vt 0.692520 0.470710 +vt 0.692520 0.502057 +vt 0.692520 0.438477 +vt 0.692520 0.453555 +vt 0.725542 0.453555 +vt 0.725542 0.438477 +vt 0.692520 0.518117 +vt 0.692520 0.533770 +vt 0.725542 0.533770 +vt 0.725542 0.518117 +vt 0.729861 0.438477 +vt 0.729861 0.407616 +vt 0.729861 0.453555 +vt 0.729861 0.438477 +vt 0.729861 0.502057 +vt 0.729861 0.470710 +vt 0.729861 0.533770 +vt 0.729861 0.518117 +vt 0.725542 0.502057 +vt 0.729861 0.502057 +vt 0.692520 0.502057 +vt 0.692520 0.470710 +vt 0.725542 0.470710 +vt 0.729861 0.470710 +vt 0.939885 0.344344 +vt 0.939386 0.324138 +vt 0.899998 0.325679 +vt 0.900142 0.343649 +vt 0.941715 0.371086 +vt 0.868319 0.324892 +vt 0.868660 0.345654 +vt 0.843504 0.298977 +vt 0.844879 0.325713 +vt 0.844879 0.325713 +vt 0.845249 0.345712 +vt 0.844917 0.372430 +vt 0.845249 0.345712 +vt 0.692520 0.407616 +vt 0.692520 0.438477 +vt 0.725542 0.438477 +vt 0.725542 0.407616 +vt 0.725542 0.502057 +vt 0.725542 0.470710 +vt 0.692520 0.470710 +vt 0.692520 0.502057 +vt 0.692520 0.438477 +vt 0.692520 0.453555 +vt 0.725542 0.453555 +vt 0.725542 0.438477 +vt 0.692520 0.518117 +vt 0.692520 0.533770 +vt 0.725542 0.533770 +vt 0.725542 0.518117 +vt 0.729861 0.438477 +vt 0.729861 0.407616 +vt 0.729861 0.453555 +vt 0.729861 0.438477 +vt 0.729861 0.502057 +vt 0.729861 0.470710 +vt 0.729861 0.533770 +vt 0.729861 0.518117 +vt 0.725542 0.502057 +vt 0.729861 0.502057 +vt 0.692520 0.502057 +vt 0.692520 0.470710 +vt 0.725542 0.470710 +vt 0.729861 0.470710 +vt 0.939885 0.344344 +vt 0.939386 0.324138 +vt 0.899998 0.325679 +vt 0.900142 0.343649 +vt 0.868319 0.324892 +vt 0.868660 0.345654 +vt 0.844879 0.325713 +vt 0.845249 0.345712 +vt 0.692520 0.407616 +vt 0.692520 0.438477 +vt 0.725542 0.438477 +vt 0.725542 0.407616 +vt 0.725542 0.502057 +vt 0.725542 0.470710 +vt 0.692520 0.470710 +vt 0.692520 0.502057 +vt 0.692520 0.438477 +vt 0.692520 0.453555 +vt 0.725542 0.453555 +vt 0.725542 0.438477 +vt 0.692520 0.518117 +vt 0.692520 0.533770 +vt 0.725542 0.533770 +vt 0.725542 0.518117 +vt 0.729861 0.438477 +vt 0.729861 0.407616 +vt 0.729861 0.453555 +vt 0.729861 0.438477 +vt 0.729861 0.502057 +vt 0.729861 0.470710 +vt 0.729861 0.533770 +vt 0.729861 0.518117 +vt 0.725542 0.502057 +vt 0.729861 0.502057 +vt 0.692520 0.502057 +vt 0.692520 0.470710 +vt 0.725542 0.470710 +vt 0.729861 0.470710 +vt 0.734306 0.534676 +vt 0.783426 0.534676 +vt 0.783469 0.485554 +vt 0.734878 0.485438 +vt 0.832497 0.535082 +vt 0.832123 0.485559 +vt 0.783453 0.436543 +vt 0.734464 0.436118 +vt 0.832472 0.436098 +vt 0.872792 0.613786 +vt 0.853734 0.612237 +vt 0.853944 0.628134 +vt 0.870679 0.629661 +vt 0.835799 0.613290 +vt 0.838282 0.628773 +vt 0.967861 0.746371 +vt 0.969147 0.727937 +vt 0.954108 0.728889 +vt 0.952982 0.745012 +vt 0.967303 0.710321 +vt 0.952588 0.713255 +vt 0.962781 0.693028 +vt 0.948703 0.697853 +vt 0.955664 0.677432 +vt 0.942633 0.684041 +vt 0.946876 0.660263 +vt 0.935073 0.669134 +vt 0.935251 0.646255 +vt 0.924899 0.657066 +vt 0.921006 0.634921 +vt 0.912418 0.647359 +vt 0.904397 0.625334 +vt 0.897971 0.639224 +vt 0.889902 0.617976 +vt 0.885409 0.633104 +vt 0.901593 0.655573 +vt 0.912975 0.663108 +vt 0.912975 0.663108 +vt 0.922153 0.673181 +vt 0.922153 0.673181 +vt 0.928680 0.686158 +vt 0.933454 0.698178 +vt 0.933454 0.698178 +vt 0.936531 0.711408 +vt 0.937902 0.724912 +vt 0.937902 0.724912 +vt 0.937623 0.739175 +vt 0.835075 0.645702 +vt 0.848902 0.644436 +vt 0.863596 0.644341 +vt 0.876775 0.645744 +vt 0.876775 0.645744 +vt 0.888347 0.649729 +vt 0.962965 0.664735 +vt 0.912697 0.478665 +vt 0.886088 0.455685 +vt 0.908393 0.456087 +vt 0.905257 0.438109 +vt 0.894941 0.438976 +vt 0.887521 0.462014 +vt 0.884886 0.480072 +vt 0.907170 0.479053 +vt 0.897857 0.462595 +vt 0.957304 0.454633 +vt 0.913207 0.453729 +vt 0.886088 0.455685 +vt 0.908393 0.456087 +vt 0.905257 0.438109 +vt 0.894941 0.438976 +vt 0.887521 0.462014 +vt 0.884886 0.480072 +vt 0.907170 0.479053 +vt 0.897857 0.462595 +vt 0.847207 0.533353 +vt 0.855382 0.534309 +vt 0.855548 0.527683 +vt 0.848043 0.526833 +vt 0.855382 0.534309 +vt 0.862861 0.534750 +vt 0.862763 0.528041 +vt 0.855548 0.527683 +vt 0.862861 0.534750 +vt 0.870463 0.534212 +vt 0.870024 0.527636 +vt 0.862763 0.528041 +vt 0.870463 0.534212 +vt 0.878425 0.532921 +vt 0.877410 0.526607 +vt 0.870024 0.527636 +vt 0.878425 0.532921 +vt 0.886229 0.530982 +vt 0.884225 0.525367 +vt 0.877410 0.526607 +vt 0.838944 0.531680 +vt 0.847207 0.533353 +vt 0.848043 0.526833 +vt 0.840988 0.525864 +vt 0.852208 0.453817 +vt 0.953388 0.382203 +vt 0.962381 0.379198 +vt 0.953391 0.379190 +vt 0.953388 0.382203 +vt 0.962377 0.382209 +vt 0.953390 0.413333 +vt 0.962382 0.413326 +vt 0.962378 0.409832 +vt 0.953389 0.409837 +vt 0.917809 0.379193 +vt 0.908728 0.379195 +vt 0.908728 0.382209 +vt 0.917808 0.382206 +vt 0.908738 0.413338 +vt 0.917814 0.413327 +vt 0.917813 0.409832 +vt 0.908733 0.409840 +vt 0.926085 0.379194 +vt 0.926084 0.382207 +vt 0.926086 0.413331 +vt 0.926087 0.409835 +vt 0.934492 0.379190 +vt 0.934492 0.382204 +vt 0.934492 0.413330 +vt 0.934492 0.409835 +vt 0.943951 0.379193 +vt 0.943949 0.382205 +vt 0.943950 0.413327 +vt 0.943950 0.409833 +vt 0.868850 0.504464 +vt 0.875480 0.503975 +vt 0.874188 0.479446 +vt 0.868089 0.479673 +vt 0.862513 0.504601 +vt 0.868850 0.504464 +vt 0.868089 0.479673 +vt 0.862369 0.479702 +vt 0.856262 0.504404 +vt 0.862513 0.504601 +vt 0.862369 0.479702 +vt 0.856737 0.479596 +vt 0.849604 0.504006 +vt 0.856262 0.504404 +vt 0.856737 0.479596 +vt 0.850631 0.479418 +vt 0.843080 0.503581 +vt 0.849604 0.504006 +vt 0.850631 0.479418 +vt 0.844681 0.479216 +vt 0.875480 0.503975 +vt 0.881858 0.503313 +vt 0.880025 0.479065 +vt 0.874188 0.479446 +vt 0.873834 0.461243 +vt 0.867888 0.461225 +vt 0.867888 0.461225 +vt 0.862304 0.461148 +vt 0.862304 0.461148 +vt 0.856808 0.461156 +vt 0.856808 0.461156 +vt 0.850857 0.461215 +vt 0.850857 0.461215 +vt 0.845260 0.461190 +vt 0.879332 0.461065 +vt 0.873834 0.461243 +vt 0.869830 0.402958 +vt 0.889269 0.401140 +vt 0.887019 0.394049 +vt 0.865905 0.392227 +vt 0.838928 0.405387 +vt 0.852082 0.404251 +vt 0.850252 0.386839 +vt 0.838249 0.383533 +vt 0.842487 0.424429 +vt 0.855934 0.421234 +vt 0.874402 0.417067 +vt 0.892210 0.410176 +vt 0.860312 0.451857 +vt 0.868700 0.443409 +vt 0.879060 0.431597 +vt 0.896100 0.422242 +vt 0.869830 0.402958 +vt 0.865905 0.392227 +vt 0.887019 0.394049 +vt 0.889269 0.401140 +vt 0.838928 0.405387 +vt 0.838249 0.383533 +vt 0.850252 0.386839 +vt 0.852082 0.404251 +vt 0.842487 0.424429 +vt 0.855934 0.421234 +vt 0.874402 0.417068 +vt 0.892210 0.410176 +vt 0.860312 0.451857 +vt 0.868700 0.443409 +vt 0.879060 0.431597 +vt 0.896100 0.422242 +vt 0.888298 0.385681 +vt 0.868699 0.380390 +vt 0.865905 0.392227 +vt 0.887019 0.394049 +vt 0.886975 0.440720 +vt 0.900786 0.429337 +vt 0.896100 0.422242 +vt 0.879060 0.431597 +vt 0.633364 0.720187 +vt 0.638579 0.720186 +vt 0.638579 0.712444 +vt 0.633364 0.712444 +vt 0.644023 0.720186 +vt 0.644023 0.712444 +vt 0.649218 0.720186 +vt 0.649218 0.712444 +vt 0.654439 0.720187 +vt 0.654439 0.712444 +vt 0.659886 0.720187 +vt 0.659886 0.712444 +vt 0.664767 0.720187 +vt 0.664767 0.712444 +vt 0.669811 0.720187 +vt 0.669811 0.712444 +vt 0.674133 0.720187 +vt 0.674133 0.712444 +vt 0.678858 0.720187 +vt 0.678858 0.712444 +vt 0.628491 0.720187 +vt 0.628491 0.712444 +vt 0.792183 0.720849 +vt 0.787280 0.722822 +vt 0.792578 0.733394 +vt 0.798474 0.732060 +vt 0.781962 0.724601 +vt 0.783205 0.735394 +vt 0.776964 0.725977 +vt 0.777420 0.736479 +vt 0.772509 0.726942 +vt 0.772195 0.738293 +vt 0.768639 0.727374 +vt 0.767334 0.739780 +vt 0.765173 0.727165 +vt 0.762744 0.740196 +vt 0.761857 0.726292 +vt 0.758332 0.739158 +vt 0.758568 0.724783 +vt 0.754157 0.736648 +vt 0.755269 0.722753 +vt 0.750471 0.733127 +vt 0.796793 0.718681 +vt 0.804814 0.731182 +vt 0.784474 0.708630 +vt 0.781942 0.710985 +vt 0.778738 0.712706 +vt 0.775456 0.713462 +vt 0.772572 0.713380 +vt 0.770099 0.712831 +vt 0.767818 0.712139 +vt 0.765544 0.711481 +vt 0.763215 0.710934 +vt 0.760759 0.710454 +vt 0.786309 0.706096 +vt 0.778292 0.701635 +vt 0.777305 0.703052 +vt 0.775945 0.704020 +vt 0.774335 0.704328 +vt 0.772685 0.704039 +vt 0.771079 0.703415 +vt 0.769474 0.702737 +vt 0.767834 0.702192 +vt 0.766168 0.701851 +vt 0.764476 0.701658 +vt 0.779008 0.700064 +vt 0.755121 0.691118 +vt 0.754541 0.691970 +vt 0.765745 0.698617 +vt 0.767342 0.693549 +vt 0.751732 0.687455 +vt 0.752680 0.687988 +vt 0.757282 0.675638 +vt 0.751968 0.674424 +vt 0.753539 0.688588 +vt 0.754373 0.689200 +vt 0.764915 0.683031 +vt 0.761753 0.678759 +vt 0.754373 0.689200 +vt 0.755030 0.690070 +vt 0.766882 0.688114 +vt 0.764915 0.683031 +vt 0.848080 0.649470 +vt 0.633364 0.720187 +vt 0.633364 0.712444 +vt 0.638579 0.712444 +vt 0.638579 0.720186 +vt 0.644023 0.712444 +vt 0.644023 0.720186 +vt 0.649218 0.712444 +vt 0.649218 0.720186 +vt 0.654439 0.712444 +vt 0.654439 0.720187 +vt 0.659886 0.712444 +vt 0.659886 0.720187 +vt 0.664767 0.712444 +vt 0.664767 0.720187 +vt 0.669811 0.712444 +vt 0.669811 0.720187 +vt 0.674133 0.712444 +vt 0.674133 0.720187 +vt 0.678858 0.712444 +vt 0.678858 0.720187 +vt 0.628491 0.720187 +vt 0.628491 0.712444 +vt 0.752426 0.669395 +vt 0.792183 0.720849 +vt 0.798474 0.732060 +vt 0.792578 0.733394 +vt 0.787280 0.722822 +vt 0.783205 0.735394 +vt 0.781962 0.724601 +vt 0.777420 0.736479 +vt 0.776964 0.725977 +vt 0.772195 0.738293 +vt 0.772509 0.726942 +vt 0.767334 0.739780 +vt 0.768639 0.727374 +vt 0.762744 0.740196 +vt 0.765173 0.727165 +vt 0.758332 0.739158 +vt 0.761857 0.726292 +vt 0.754157 0.736648 +vt 0.758568 0.724783 +vt 0.750471 0.733127 +vt 0.755269 0.722753 +vt 0.796793 0.718681 +vt 0.804814 0.731182 +vt 0.781942 0.710985 +vt 0.784474 0.708630 +vt 0.778738 0.712706 +vt 0.775456 0.713462 +vt 0.772572 0.713380 +vt 0.770099 0.712831 +vt 0.767818 0.712139 +vt 0.765544 0.711481 +vt 0.763215 0.710934 +vt 0.760759 0.710454 +vt 0.786309 0.706096 +vt 0.777305 0.703052 +vt 0.778292 0.701635 +vt 0.775945 0.704020 +vt 0.774335 0.704328 +vt 0.772685 0.704039 +vt 0.771079 0.703415 +vt 0.769474 0.702737 +vt 0.767834 0.702192 +vt 0.766168 0.701851 +vt 0.764476 0.701658 +vt 0.779008 0.700064 +vt 0.755121 0.691118 +vt 0.767342 0.693549 +vt 0.765745 0.698617 +vt 0.754541 0.691970 +vt 0.751732 0.687455 +vt 0.751968 0.674424 +vt 0.757282 0.675638 +vt 0.752680 0.687988 +vt 0.753539 0.688588 +vt 0.761753 0.678759 +vt 0.764915 0.683031 +vt 0.754373 0.689200 +vt 0.754373 0.689200 +vt 0.764915 0.683031 +vt 0.766882 0.688114 +vt 0.755030 0.690070 +vt 0.853693 0.205576 +vt 0.853693 0.205576 +vn 1.0000 0.0000 -0.0000 +vn 0.0000 0.0000 1.0000 +vn -0.0000 0.0313 0.9995 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.1156 -0.3511 0.9292 +vn 0.1518 0.3370 0.9292 +vn -0.1874 0.2767 0.9425 +vn -0.8790 -0.4768 0.0001 +vn 0.8738 0.4863 0.0001 +vn -0.8737 -0.4864 0.0002 +vn 0.0000 -0.5024 -0.8647 +vn 0.0000 -0.9998 0.0211 +vn 0.0000 0.9997 0.0225 +vn 0.0000 -0.9939 0.1103 +vn 0.0000 0.7995 -0.6007 +vn 0.0000 0.4390 0.8985 +vn -0.8795 -0.4672 -0.0905 +vn 0.8795 -0.4672 -0.0905 +vn 0.0000 -0.8265 0.5629 +vn 0.0000 0.9021 0.4314 +vn -0.8660 0.4941 -0.0765 +vn -0.4871 0.4155 -0.7682 +vn 0.4871 0.4155 -0.7682 +vn 0.8660 0.4941 -0.0765 +vn 0.4693 0.6707 0.5743 +vn -0.4693 0.6707 0.5743 +vn 0.0000 0.9882 -0.1531 +vn 0.0000 0.8388 -0.5444 +vn 0.0000 -0.9879 0.1551 +vn 0.9428 -0.3296 0.0510 +vn 0.5172 -0.5404 -0.6637 +vn -0.5172 -0.5404 -0.6637 +vn -0.9428 -0.3296 0.0510 +vn -0.5444 -0.1925 0.8165 +vn 0.5444 -0.1925 0.8165 +vn 0.0000 -0.9111 0.4121 +vn 0.9998 -0.0213 0.0000 +vn -0.9999 -0.0129 0.0000 +vn 0.9992 0.0390 0.0002 +vn 0.9992 0.0393 0.0000 +vn -0.9949 -0.1012 -0.0004 +vn -0.9949 -0.1007 0.0000 +vn 0.0000 0.3128 0.9498 +vn -0.0000 0.1355 -0.9908 +vn 0.0000 -0.9987 0.0515 +vn 0.0000 0.1600 0.9871 +vn -0.0000 0.0502 -0.9987 +vn -0.0000 0.9965 0.0838 +vn 0.0000 -0.9971 0.0755 +vn -0.1977 -0.1115 0.9739 +vn -0.1314 -0.1329 -0.9824 +vn 0.0000 0.0157 0.9999 +vn -0.0000 0.0360 -0.9994 +vn -0.1977 0.1115 0.9739 +vn -0.1314 0.1329 -0.9824 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.7313 -0.6820 +vn 0.0000 -0.7094 -0.7048 +vn 0.0000 0.9890 0.1477 +vn 0.0000 -0.7313 -0.6820 +vn 0.0000 0.7094 -0.7048 +vn 0.0000 -0.9890 0.1477 +vn 0.0000 -0.3673 0.9301 +vn 0.0000 -0.9980 0.0638 +vn 0.0000 0.9769 0.2137 +vn 0.0000 -0.6862 0.7274 +vn 0.0000 -0.6597 -0.7516 +vn 0.0000 0.6454 0.7638 +vn 0.0000 0.1771 0.9842 +vn 0.0000 -0.9869 0.1612 +vn 0.0000 0.4954 -0.8687 +vn 0.0000 0.8983 0.4393 +vn 0.9837 0.0191 0.1787 +vn 0.1589 -0.5349 -0.8298 +vn 0.1488 0.4938 -0.8567 +vn 0.0127 0.9887 -0.1492 +vn 0.0339 -0.9938 -0.1063 +vn -0.4992 -0.8280 0.2554 +vn -0.4992 -0.8280 0.2553 +vn 0.3675 -0.9184 0.1464 +vn 0.3676 -0.9184 0.1464 +vn -0.9471 0.3130 0.0707 +vn -0.7533 -0.6349 -0.1714 +vn 0.1856 -0.9303 -0.3164 +vn 0.7443 0.6675 0.0215 +vn -0.1976 0.9659 0.1676 +vn 0.0718 -0.2009 0.9770 +vn 0.9786 -0.2056 -0.0000 +vn 0.1850 -0.0389 0.9820 +vn -0.9786 0.2056 0.0000 +vn 0.1785 0.9839 0.0000 +vn -0.1785 -0.9839 0.0000 +vn -0.0052 -0.0289 0.9996 +vn -0.0050 -0.0277 -0.9996 +vn 0.9662 -0.2030 -0.1586 +vn -0.9662 0.2030 -0.1586 +vn -0.1737 -0.9577 0.2293 +vn 0.1737 0.9577 0.2293 +vn -0.0124 0.0026 -0.9999 +vn 0.3573 -0.9340 -0.0000 +vn 0.0272 -0.0911 0.9955 +vn -0.9995 -0.0317 0.0000 +vn 0.1685 -0.9857 0.0045 +vn -0.1685 0.9857 0.0045 +vn -0.8086 -0.0029 -0.5884 +vn 0.8390 0.0031 0.5442 +vn 0.1465 -0.9892 0.0034 +vn -0.1465 0.9892 0.0034 +vn 0.8044 0.0378 -0.5929 +vn -0.8351 -0.0392 0.5487 +vn 0.1693 -0.9855 0.0044 +vn -0.1693 0.9855 0.0044 +vn -0.8088 -0.0014 -0.5881 +vn 0.8391 0.0015 0.5440 +vn 0.1510 -0.9885 0.0033 +vn -0.1510 0.9885 0.0033 +vn 0.8052 0.0315 -0.5921 +vn -0.8359 -0.0327 0.5479 +vn -0.8567 0.4633 0.2268 +vn -0.4744 -0.8774 -0.0718 +vn 0.8781 -0.4748 -0.0584 +vn 0.4618 0.8541 0.2392 +vn -0.4433 -0.8654 0.2338 +vn 0.8871 -0.4544 -0.0812 +vn 0.4549 0.8881 -0.0659 +vn -0.8622 0.4417 0.2479 +vn 0.5181 0.8206 0.2413 +vn -0.8433 0.5325 -0.0730 +vn -0.5324 -0.8432 -0.0741 +vn 0.8208 -0.5182 0.2404 +vn 0.8552 -0.4648 0.2293 +vn 0.4763 0.8765 -0.0692 +vn -0.8770 0.4766 -0.0610 +vn -0.4639 -0.8536 0.2368 +vn -0.4433 -0.8653 0.2338 +vn 0.4764 0.8765 -0.0692 +vn 0.9998 -0.0184 0.0000 +vn -0.9997 0.0244 0.0000 +vn 0.8216 -0.4341 0.3695 +vn -0.4420 0.2335 -0.8661 +vn 0.7821 -0.4132 -0.4665 +vn -0.6136 0.3242 0.7199 +vn 0.8519 0.3712 0.3695 +vn 0.8519 0.3711 0.3695 +vn -0.4583 -0.1997 -0.8661 +vn -0.4583 -0.1996 -0.8661 +vn 0.8109 0.3533 -0.4665 +vn -0.6363 -0.2772 0.7199 +vn -0.0000 1.0000 0.0022 +vn 0.9996 0.0000 0.0274 +vn 0.9960 0.0000 -0.0897 +vn 0.9994 0.0000 0.0332 +vn 0.9941 0.0000 -0.1085 +vn 0.0000 -0.0855 0.9963 +vn 0.0000 -0.9971 0.0767 +vn 0.0000 0.0855 0.9963 +vn -0.0000 -0.9935 -0.1139 +vn 0.0000 0.9971 0.0767 +vn 0.0000 -0.0016 1.0000 +vn 0.0144 -0.9999 0.0000 +vn -0.0144 0.9999 -0.0000 +vn 1.0000 -0.0084 -0.0000 +vn -1.0000 0.0084 0.0000 +vn -0.9998 0.0174 0.0000 +vn 0.9998 -0.0174 0.0000 +vn 0.0757 -0.0167 0.9970 +vn 0.0493 0.0699 0.9963 +vn 0.8316 -0.5473 0.0948 +vn -0.1299 -0.0154 -0.9914 +vn -0.6744 0.5072 0.5367 +vn -0.6744 0.5072 0.5366 +vn 0.0782 0.0548 0.9954 +vn -0.8055 -0.5607 0.1918 +vn 0.0016 0.0008 -1.0000 +vn 0.7313 0.5092 0.4539 +vn -0.2787 0.5816 0.7642 +vn 0.3272 -0.7801 -0.5333 +vn 0.2838 0.8393 -0.4637 +vn -0.6730 0.5082 -0.5374 +vn -0.9874 -0.1488 0.0547 +vn -0.4541 -0.4942 0.7413 +vn -0.4541 -0.4941 0.7413 +vn 0.5432 -0.1466 0.8267 +vn 0.8446 0.5020 0.1860 +vn 0.3806 0.6847 -0.6215 +vn -0.6261 0.3363 -0.7035 +vn -0.9317 -0.3541 -0.0811 +vn -0.3602 -0.7241 0.5882 +vn 0.6403 -0.3780 0.6687 +vn 0.9484 0.3127 0.0530 +vn 0.1122 -0.9462 0.3035 +vn -0.1702 0.9853 0.0125 +vn 0.3968 -0.7641 0.5087 +vn 0.2518 0.6468 0.7199 +vn 0.2519 0.6468 0.7199 +vn -0.3760 -0.3489 -0.8584 +vn -0.3760 -0.3488 -0.8585 +vn 0.1122 0.9462 0.3035 +vn -0.1702 -0.9853 0.0125 +vn 0.3968 0.7641 0.5087 +vn 0.2518 -0.6468 0.7199 +vn 0.2519 -0.6468 0.7199 +vn -0.3760 0.3489 -0.8584 +vn -0.3760 0.3488 -0.8585 +vn 0.8663 0.4992 -0.0163 +vn -0.8662 -0.4995 0.0170 +vn -0.8662 -0.4994 0.0165 +vn -0.3015 -0.9535 0.0004 +vn 0.3012 0.9536 0.0004 +vn 0.3014 0.9535 -0.0002 +vn -0.8790 -0.4768 0.0002 +vn 0.8790 0.4769 0.0002 +vn 0.8790 0.4768 0.0004 +vn 0.8738 0.4864 0.0002 +vn -0.8737 -0.4865 0.0002 +vn -0.8738 -0.4863 0.0004 +vn 0.9844 0.0500 -0.1685 +vn 0.9790 0.0307 0.2015 +vn 0.9966 -0.0360 -0.0735 +vn 0.9837 -0.1796 0.0055 +vn 0.9991 -0.0390 0.0147 +vn 0.9804 0.1357 0.1430 +vn -0.9844 0.0500 -0.1685 +vn -0.9966 -0.0360 -0.0735 +vn -0.9790 0.0307 0.2015 +vn -0.9837 -0.1796 0.0055 +vn -0.9804 0.1357 0.1430 +vn -0.9991 -0.0390 0.0147 +vn 0.0000 0.9470 0.3213 +vn 0.0000 0.6646 0.7472 +vn 0.0000 0.7815 0.6240 +vn 0.0000 0.2168 0.9762 +vn -0.9919 0.0096 0.1267 +vn -0.9984 -0.0463 -0.0310 +vn -0.9995 0.0068 0.0293 +vn 0.9987 -0.0110 -0.0494 +vn 0.9993 0.0257 -0.0269 +vn 0.9988 0.0332 0.0372 +vn 0.0246 -0.2768 0.9606 +vn 0.1356 -0.0967 0.9860 +vn 0.0540 -0.2510 0.9665 +vn 0.1461 -0.0797 0.9861 +vn -0.0172 0.2840 0.9587 +vn 0.1063 0.4474 0.8880 +vn 0.0998 0.1117 0.9887 +vn -0.0778 -0.3607 0.9294 +vn -0.1053 -0.2560 0.9609 +vn -0.1723 -0.0398 0.9842 +vn 0.1033 -0.3962 0.9124 +vn 0.0295 0.2056 0.9782 +vn -0.0790 0.1318 0.9881 +vn -0.0180 0.4222 0.9063 +vn -0.1864 -0.9804 0.0639 +vn -0.1429 -0.9884 0.0508 +vn -0.1792 -0.9811 0.0727 +vn -0.1688 0.9852 0.0296 +vn -0.1831 0.9828 0.0232 +vn -0.1335 0.9910 0.0078 +vn -0.1328 0.9906 0.0336 +vn -0.1186 0.9922 0.0390 +vn -0.1730 0.9842 0.0378 +vn -0.1645 -0.9831 0.0799 +vn -0.1571 -0.9827 0.0975 +vn -0.1244 -0.9894 0.0751 +vn -0.0781 0.9965 -0.0297 +vn -0.0516 0.9972 -0.0543 +vn -0.1007 0.9949 0.0081 +vn -0.0962 -0.9947 0.0363 +vn -0.1093 -0.9937 0.0259 +vn -0.0618 -0.9978 -0.0245 +vn 0.0943 0.9955 -0.0075 +vn 0.1075 0.9940 -0.0179 +vn 0.0584 0.9960 -0.0675 +vn 0.0764 -0.9970 0.0111 +vn 0.0494 -0.9987 -0.0128 +vn 0.1013 -0.9937 0.0486 +vn 0.1647 0.9857 0.0355 +vn 0.1582 0.9860 0.0529 +vn 0.1240 0.9918 0.0317 +vn 0.1350 -0.9881 0.0741 +vn 0.1214 -0.9894 0.0797 +vn 0.1757 -0.9814 0.0774 +vn 0.1861 0.9823 0.0194 +vn 0.1425 0.9898 0.0065 +vn 0.1793 0.9834 0.0280 +vn 0.1712 -0.9828 0.0693 +vn 0.1846 -0.9808 0.0628 +vn 0.1356 -0.9896 0.0486 +vn 0.7291 -0.2212 0.6476 +vn 0.7482 -0.4615 0.4766 +vn 0.8978 -0.3467 0.2714 +vn 0.9256 -0.0620 0.3735 +vn 0.9191 -0.0504 0.3909 +vn 0.9319 -0.0500 0.3592 +vn 0.9166 -0.0695 0.3938 +vn 0.9140 -0.0337 0.4044 +vn 0.9248 0.0118 0.3803 +vn 0.9272 -0.0448 0.3718 +vn 0.9267 -0.0420 0.3734 +vn 0.9397 -0.0369 0.3400 +vn 0.9315 -0.0725 0.3566 +vn 0.9437 0.0260 0.3298 +vn 0.9580 -0.0503 0.2822 +vn 0.9344 -0.1262 0.3331 +vn 0.9320 -0.0539 0.3583 +vn -0.8287 0.1550 0.5379 +vn -0.4605 0.3779 0.8032 +vn -0.3675 0.9184 -0.1464 +vn 0.0390 0.5807 0.8132 +vn 0.4702 0.6812 0.5612 +vn 0.7115 0.6802 0.1765 +vn 0.7538 0.6095 -0.2454 +vn 0.7539 0.6095 -0.2454 +vn 0.5902 0.4787 -0.6500 +vn 0.2036 0.2876 -0.9358 +vn -0.3120 0.0800 -0.9467 +vn -0.7359 -0.0554 -0.6748 +vn -0.9583 -0.0809 -0.2740 +vn -0.9889 -0.0068 0.1484 +vn -0.1605 0.2148 0.9634 +vn -0.0353 0.1295 0.9909 +vn -0.0408 0.1870 0.9815 +vn -0.0183 0.1205 0.9925 +vn 0.0180 -0.1081 0.9940 +vn 0.0110 -0.1246 0.9921 +vn 0.0255 -0.2211 0.9749 +vn -0.0297 -0.2710 0.9621 +vn 0.0217 0.2143 0.9765 +vn 0.0827 -0.2383 0.9677 +vn 0.0840 0.4214 0.9030 +vn -0.0322 0.3556 0.9341 +vn -0.0082 0.1575 0.9875 +vn -0.0276 0.1891 0.9816 +vn -0.1440 0.4006 0.9049 +vn 0.0302 -0.1228 0.9920 +vn 0.0399 -0.1935 0.9803 +vn 0.0887 -0.2015 0.9755 +vn 0.0166 -0.1105 0.9937 +vn 0.0024 -0.2253 0.9743 +vn -0.1567 0.4098 0.8986 +vn 0.0029 0.1510 0.9885 +vn 0.0184 0.2915 0.9564 +vn 0.0189 0.1272 0.9917 +vn 0.0191 -0.1153 0.9932 +vn 0.0327 -0.1183 0.9924 +vn 0.0749 -0.2312 0.9700 +vn 0.0400 -0.2567 0.9657 +vn 0.2229 0.3830 0.8965 +vn 0.1662 -0.2733 0.9475 +vn -0.7482 0.2086 -0.6298 +vn -0.2983 0.4925 -0.8176 +vn -0.3586 -0.1069 -0.9273 +vn -0.7398 -0.3475 -0.5762 +vn -0.2882 -0.6532 -0.7002 +vn 0.1868 -0.4007 -0.8970 +vn 0.1882 0.1938 -0.9628 +vn 0.5119 0.5842 0.6298 +vn -0.0200 0.5755 0.8176 +vn 0.3586 0.1069 0.9273 +vn -0.2636 0.0591 0.9628 +vn 0.0631 -0.4376 0.8970 +vn 0.5988 -0.3888 0.7002 +vn 0.8093 0.1142 0.5762 +vn -0.3394 0.8854 -0.3176 +vn -0.0302 0.7209 -0.6923 +vn -0.5768 0.5130 -0.6357 +vn -0.7699 0.6132 -0.1768 +vn -0.9129 0.1516 -0.3790 +vn -0.6401 -0.0577 -0.7661 +vn -0.1757 0.2373 -0.9554 +vn 0.9047 -0.3400 0.2567 +vn 0.7993 0.0248 0.6004 +vn 0.5768 -0.5130 0.6357 +vn 0.3636 -0.0559 0.9299 +vn 0.0278 -0.5353 0.8442 +vn 0.1699 -0.8707 0.4616 +vn 0.6030 -0.7737 0.1944 +vn 0.8714 -0.4712 0.1369 +vn 0.8896 0.4309 0.1515 +vn 0.9383 -0.2905 0.1879 +vn 0.4712 0.8714 0.1369 +vn -0.4310 0.8896 0.1515 +vn 0.2905 0.9383 0.1879 +vn -0.8713 0.4712 0.1369 +vn -0.8896 -0.4309 0.1515 +vn -0.9383 0.2904 0.1879 +vn -0.0512 0.3574 0.9325 +vn -0.2406 0.2479 0.9384 +vn -0.3288 0.0571 0.9427 +vn -0.2956 -0.1452 0.9442 +vn -0.1558 -0.2948 0.9428 +vn 0.0489 -0.3416 0.9386 +vn 0.2512 -0.2588 0.9327 +vn 0.3688 -0.0640 0.9273 +vn 0.3410 0.1674 0.9250 +vn 0.1750 0.3312 0.9272 +vn -0.3112 0.1830 0.9325 +vn -0.3439 -0.0334 0.9384 +vn -0.2497 -0.2213 0.9427 +vn -0.0710 -0.3215 0.9442 +vn 0.1332 -0.3057 0.9428 +vn 0.2975 -0.1749 0.9386 +vn 0.3590 0.0348 0.9327 +vn 0.2802 0.2482 0.9273 +vn 0.0820 0.3709 0.9250 +vn -0.1496 0.3434 0.9272 +vn -0.1740 0.1188 0.9775 +vn -0.1868 0.0049 0.9824 +vn -0.0105 0.0311 0.9995 +vn -0.1296 -0.0978 0.9867 +vn -0.0228 -0.1473 0.9888 +vn 0.0900 -0.1261 0.9879 +vn 0.1725 -0.0416 0.9841 +vn 0.1879 0.0784 0.9791 +vn 0.1303 0.1817 0.9747 +vn 0.0229 0.2315 0.9726 +vn -0.0957 0.2073 0.9736 +vn 0.3381 -0.9397 -0.0515 +vn 0.8662 0.4994 -0.0173 +vn 0.8661 0.4995 -0.0181 +vn 0.8663 0.4993 -0.0169 +vn -0.8663 -0.4993 0.0163 +vn -0.8663 -0.4993 0.0162 +vn -0.8664 -0.4992 0.0151 +vn -0.3014 -0.9535 -0.0003 +vn -0.3011 -0.9536 -0.0015 +vn -0.3014 -0.9535 -0.0002 +vn 0.3015 0.9535 -0.0007 +vn 0.3014 0.9535 -0.0005 +vn 0.3016 0.9534 -0.0015 +vn 0.0000 0.4367 -0.8996 +vn 0.0123 0.9997 -0.0213 +vn 0.0022 1.0000 0.0000 +vn -0.0029 0.9995 -0.0320 +vn -0.0009 0.9987 -0.0519 +vn 0.0150 -0.0011 0.9999 +vn 0.0357 -0.0027 0.9994 +vn 0.0489 -0.0038 0.9988 +vn 0.1265 -0.0097 -0.9919 +vn 0.0022 -0.0002 -1.0000 +vn 0.0055 -0.0004 -1.0000 +vn 0.0177 -0.0014 0.9998 +vn -0.0202 0.0015 0.9998 +vn 0.0170 -0.0013 -0.9999 +vn -0.0215 0.0016 -0.9998 +vn -0.0119 0.9998 -0.0144 +vn -0.0137 0.9993 -0.0345 +vn -0.0534 0.0042 0.9986 +vn -0.0545 0.0042 0.9985 +vn -0.0517 0.0044 0.9987 +vn -0.0516 0.0045 0.9987 +vn -0.0404 0.0035 -0.9992 +vn -0.1181 0.0104 -0.9929 +vn -0.1186 0.0095 -0.9929 +vn 0.0112 -0.0009 -0.9999 +vn -0.0152 0.9999 0.0053 +vn -0.0200 0.9998 0.0000 +vn -0.0183 0.9998 0.0037 +vn -0.0137 0.9999 -0.0047 +vn -0.1116 -0.0000 0.9937 +vn 0.0311 -0.0000 0.9995 +vn -0.0340 -0.0000 0.9994 +vn 0.1116 0.0000 -0.9937 +vn 0.0340 -0.0000 -0.9994 +vn -0.0311 -0.0000 -0.9995 +vn -0.0152 0.0000 0.9999 +vn 0.0369 0.0000 0.9993 +vn -0.0369 0.0000 -0.9993 +vn 0.0152 0.0000 -0.9999 +vn 0.0303 0.0000 0.9995 +vn -0.0265 0.0000 0.9996 +vn 0.0265 -0.0000 -0.9996 +vn -0.0303 0.0000 -0.9995 +vn 0.1055 0.0000 0.9944 +vn -0.1055 0.0000 -0.9944 +vn -0.8652 0.3129 -0.3918 +vn -0.8715 0.3076 -0.3819 +vn -0.8652 0.3129 -0.3919 +vn -0.8610 0.3164 -0.3983 +vn -0.8420 -0.5366 -0.0559 +vn -0.8381 -0.5436 -0.0451 +vn -0.8445 -0.5319 -0.0629 +vn 0.8420 -0.5366 -0.0559 +vn 0.8335 -0.5493 -0.0593 +vn 0.8469 -0.5290 -0.0538 +vn 0.8652 0.3129 -0.3918 +vn 0.8692 0.2989 -0.3939 +vn 0.8627 0.3212 -0.3906 +vn 0.9336 0.0904 -0.3468 +vn 0.9498 0.0944 -0.2983 +vn 0.9567 0.0010 -0.2911 +vn 0.9503 -0.0647 -0.3044 +vn 0.9442 -0.2843 -0.1665 +vn 0.9467 -0.2423 -0.2122 +vn 0.9253 -0.3593 -0.1213 +vn 0.9252 -0.3703 -0.0831 +vn 0.9550 -0.1426 -0.2601 +vn 0.9590 -0.1528 -0.2389 +vn 1.0000 -0.0096 -0.0026 +vn 0.9835 0.0928 0.1551 +vn 0.9283 0.1388 -0.3450 +vn 0.9499 0.1145 -0.2907 +vn 0.9195 -0.3927 -0.0170 +vn 0.9092 -0.4146 -0.0377 +vn 0.9184 -0.2259 0.3248 +vn -0.9336 0.0904 -0.3468 +vn -0.9503 -0.0647 -0.3044 +vn -0.9567 0.0010 -0.2911 +vn -0.9498 0.0944 -0.2983 +vn -0.9442 -0.2843 -0.1665 +vn -0.9252 -0.3703 -0.0831 +vn -0.9253 -0.3593 -0.1213 +vn -0.9467 -0.2423 -0.2122 +vn -0.9590 -0.1528 -0.2388 +vn -0.9550 -0.1426 -0.2601 +vn -1.0000 -0.0096 -0.0026 +vn -0.9835 0.0928 0.1551 +vn -0.9499 0.1145 -0.2907 +vn -0.9283 0.1388 -0.3450 +vn -0.9092 -0.4146 -0.0377 +vn -0.9195 -0.3927 -0.0170 +vn -0.9184 -0.2259 0.3248 +vn -0.0000 1.0000 0.0050 +vn 0.0000 0.9780 -0.2086 +vn 0.0000 0.9852 0.1715 +vn 0.0000 0.8034 0.5954 +vn 0.0000 0.7134 0.7007 +vn 0.4965 0.6786 0.5413 +vn 0.5039 0.6735 0.5408 +vn 0.4689 0.7248 0.5048 +vn 0.4285 0.7516 0.5015 +vn 0.9717 0.2257 -0.0703 +vn 0.9722 0.2203 -0.0794 +vn 0.9454 0.3136 -0.0885 +vn 0.9276 0.3691 -0.0580 +vn 0.5672 -0.2062 -0.7973 +vn 0.5620 -0.1920 -0.8046 +vn 0.5601 -0.0815 -0.8244 +vn 0.5725 -0.0663 -0.8172 +vn -0.5673 -0.2061 -0.7973 +vn -0.5616 -0.1849 -0.8065 +vn -0.5601 -0.0815 -0.8244 +vn -0.5731 -0.0882 -0.8147 +vn -0.9716 0.2257 -0.0703 +vn -0.9641 0.2499 -0.0901 +vn -0.9454 0.3136 -0.0885 +vn -0.9517 0.3034 -0.0476 +vn -0.4855 0.6941 0.5315 +vn -0.5096 0.6675 0.5429 +vn -0.4689 0.7248 0.5048 +vn -0.4474 0.7172 0.5342 +vn 0.4720 0.4488 0.7588 +vn 0.3885 0.4705 0.7923 +vn 0.4331 0.6812 0.5903 +vn 0.5481 0.5957 0.5871 +vn 0.9985 0.0454 -0.0294 +vn 0.9981 -0.0605 0.0095 +vn 0.9663 0.2305 -0.1146 +vn 0.9724 0.1957 -0.1270 +vn 0.4292 -0.4041 -0.8078 +vn 0.4032 -0.5802 -0.7077 +vn 0.4421 -0.1249 -0.8882 +vn 0.5036 -0.0493 -0.8626 +vn -0.4318 -0.3622 -0.8261 +vn -0.4081 -0.5402 -0.7360 +vn -0.4421 -0.1249 -0.8882 +vn -0.5297 -0.1694 -0.8311 +vn -0.9983 0.0486 -0.0315 +vn -0.9983 -0.0578 0.0091 +vn -0.9663 0.2305 -0.1146 +vn -0.9585 0.2391 -0.1552 +vn -0.4391 0.5116 0.7385 +vn -0.4434 0.3656 0.8183 +vn -0.4331 0.6812 0.5903 +vn -0.5285 0.6946 0.4881 +vn 0.5732 0.1622 0.8032 +vn 0.5704 0.1308 0.8109 +vn 0.5721 0.1970 0.7962 +vn 1.0000 0.0029 -0.0004 +vn 0.9992 -0.0396 0.0093 +vn 0.9987 0.0501 -0.0078 +vn 0.5733 -0.1595 -0.8037 +vn 0.5707 -0.2287 -0.7887 +vn 0.5731 -0.0825 -0.8153 +vn -0.5733 -0.1608 -0.8034 +vn -0.5707 -0.2287 -0.7887 +vn -0.5731 -0.0825 -0.8153 +vn -1.0000 0.0029 -0.0004 +vn -0.9992 -0.0396 0.0093 +vn -0.9987 0.0501 -0.0078 +vn -0.5732 0.1635 0.8030 +vn -0.5704 0.1308 0.8109 +vn -0.5721 0.1970 0.7962 +vn 0.9971 -0.0752 0.0124 +vn 0.9831 -0.1808 0.0273 +vn 0.9831 -0.1808 0.0272 +vn 0.9979 -0.0643 0.0107 +vn 0.5646 -0.2953 -0.7707 +vn 0.5730 -0.1850 -0.7984 +vn 0.5556 -0.3629 -0.7481 +vn 0.5556 -0.3628 -0.7481 +vn -0.5639 -0.2953 -0.7712 +vn -0.5730 -0.1850 -0.7984 +vn -0.5556 -0.3629 -0.7481 +vn -0.9929 -0.1179 0.0183 +vn -0.9998 -0.0185 0.0045 +vn -0.9831 -0.1808 0.0272 +vn -0.9831 -0.1808 0.0273 +vn -0.5700 0.0273 0.8212 +vn -0.5723 0.1422 0.8076 +vn -0.5651 -0.0468 0.8237 +vn 0.5705 0.0272 0.8209 +vn 0.5723 0.1422 0.8076 +vn 0.5651 -0.0468 0.8237 +vn 0.9990 -0.0384 0.0207 +vn 0.9991 -0.0352 0.0228 +vn 0.9999 -0.0107 0.0070 +vn 0.9999 0.0141 -0.0040 +vn 0.5511 -0.5438 -0.6329 +vn 0.5538 -0.5419 -0.6321 +vn 0.5616 -0.4522 -0.6929 +vn 0.5656 -0.3642 -0.7399 +vn -0.5511 -0.5438 -0.6329 +vn -0.5537 -0.5435 -0.6309 +vn -0.5615 -0.4535 -0.6921 +vn -0.5655 -0.3654 -0.7394 +vn -0.9990 -0.0384 0.0207 +vn -0.9990 -0.0374 0.0243 +vn -0.9999 -0.0109 0.0071 +vn -0.9999 0.0139 -0.0040 +vn -0.5579 0.4665 0.6863 +vn -0.5572 0.4666 0.6869 +vn -0.5613 0.4301 0.7071 +vn -0.5625 0.3961 0.7258 +vn 0.5581 0.4665 0.6862 +vn 0.5628 0.4124 0.7164 +vn 0.5615 0.4131 0.7170 +vn 0.5572 0.4666 0.6869 +vn 0.9579 -0.2726 0.0898 +vn 0.9332 -0.3551 0.0558 +vn 0.3751 -0.7587 -0.5326 +vn 0.4531 -0.7062 -0.5441 +vn -0.3751 -0.7586 -0.5327 +vn -0.4156 -0.7729 -0.4795 +vn -0.9579 -0.2726 0.0898 +vn -0.9662 -0.2546 0.0400 +vn -0.4681 0.1393 0.8727 +vn -0.5724 0.1900 0.7976 +vn 0.4681 0.1393 0.8727 +vn 0.5724 0.1900 0.7976 +vn 0.9921 0.1162 -0.0474 +vn 0.9911 0.1216 -0.0550 +vn 0.5636 -0.3485 -0.7490 +vn 0.5646 -0.3499 -0.7475 +vn -0.5637 -0.3489 -0.7487 +vn -0.5646 -0.3471 -0.7488 +vn -0.9921 0.1163 -0.0474 +vn -0.9931 0.1065 -0.0482 +vn -0.5289 0.6149 0.5850 +vn -0.5329 0.6222 0.5735 +vn 0.5289 0.6149 0.5850 +vn 0.5329 0.6222 0.5735 +vn 0.4697 -0.8410 0.2686 +vn 0.3625 -0.8378 0.4083 +vn 0.4872 -0.8733 0.0031 +vn 0.4065 -0.9020 -0.1455 +vn 0.2671 0.9624 -0.0490 +vn 0.2116 0.9384 -0.2732 +vn 0.2763 0.9392 -0.2037 +vn 0.2253 0.9737 0.0348 +vn 0.1224 -0.9444 -0.3052 +vn -0.0788 -0.9484 -0.3072 +vn -0.0393 0.9340 -0.3552 +vn 0.0673 0.9324 -0.3552 +vn -0.3868 -0.9058 -0.1729 +vn -0.4668 -0.8838 -0.0310 +vn -0.2636 0.9390 -0.2210 +vn -0.1957 0.9352 -0.2951 +vn -0.4871 -0.8399 0.2395 +vn -0.3870 -0.8431 0.3733 +vn -0.2372 0.9713 0.0174 +vn -0.2809 0.9571 -0.0710 +vn -0.1221 -0.8376 0.5324 +vn 0.0767 -0.8418 0.5343 +vn 0.0443 0.9928 0.1109 +vn -0.0721 0.9913 0.1102 +vn 0.9988 -0.0489 0.0090 +vn 1.0000 0.0067 -0.0048 +vn 0.5642 -0.2943 -0.7714 +vn 0.5641 -0.2694 -0.7805 +vn -0.5642 -0.2944 -0.7714 +vn -0.5640 -0.2691 -0.7807 +vn -0.9988 -0.0478 0.0088 +vn -1.0000 0.0069 -0.0049 +vn -0.5702 0.1814 0.8012 +vn -0.5665 0.2831 0.7739 +vn 0.5703 0.1799 0.8015 +vn 0.5672 0.2832 0.7733 +vn 0.9982 0.0593 -0.0063 +vn 0.9982 0.0594 -0.0062 +vn 0.5745 -0.0960 -0.8129 +vn 0.5746 -0.0959 -0.8128 +vn -0.5745 -0.0960 -0.8128 +vn -0.5746 -0.0960 -0.8128 +vn -0.9982 0.0592 -0.0063 +vn -0.9982 0.0592 -0.0062 +vn -0.5685 0.2300 0.7899 +vn -0.5684 0.2300 0.7900 +vn 0.5685 0.2301 0.7899 +vn 0.5684 0.2301 0.7899 +vn 0.9972 -0.0405 -0.0630 +vn 0.9980 -0.0626 0.0000 +vn -0.0035 0.1191 0.9929 +vn -0.0046 0.0798 0.9968 +vn -0.0020 0.0780 0.9970 +vn 0.0000 0.1241 0.9923 +vn -0.9953 0.0564 0.0785 +vn -0.9996 0.0297 0.0000 +vn -0.0036 -0.0446 0.9990 +vn -0.0031 -0.1100 0.9939 +vn -0.9990 -0.0224 -0.0400 +vn 0.9990 0.0050 0.0452 +vn 0.9999 -0.0171 0.0000 +vn -0.0056 -0.1839 0.9829 +vn 0.0000 -0.1964 0.9805 +vn -0.0047 0.1973 0.9803 +vn -0.0195 0.1797 0.9835 +vn 0.0080 0.2055 0.9786 +vn 0.0084 0.1952 0.9807 +vn -0.1079 0.9942 -0.0000 +vn 0.0292 0.9996 0.0000 +vn 0.0291 0.9996 -0.0000 +vn -0.0124 -0.9999 0.0000 +vn -0.0621 -0.9981 0.0000 +vn 0.0282 -0.9996 0.0000 +vn 0.0618 -0.9981 0.0000 +vn 0.1318 0.9913 0.0000 +vn 0.0067 0.1876 0.9822 +vn -0.0101 0.1637 0.9865 +vn 0.0035 -0.1633 0.9866 +vn 0.0170 -0.1409 0.9899 +vn -0.0024 -0.1731 0.9849 +vn -0.0058 -0.1776 0.9841 +vn -0.9948 -0.1014 -0.0006 +vn -0.1198 0.9928 0.0057 +vn -0.0173 0.9998 0.0058 +vn -0.0318 -0.9995 -0.0000 +vn -0.0791 -0.9969 0.0000 +vn 0.0136 -0.9999 -0.0000 +vn -0.0019 -0.1708 0.9853 +vn 0.0087 -0.1518 0.9884 +vn 0.0569 -0.9984 0.0000 +vn 0.0775 0.9970 0.0058 +vn 0.9939 0.0075 0.1096 +vn 0.9940 0.0080 0.1093 +vn 0.9968 0.0055 0.0794 +vn 0.9996 0.0017 0.0279 +vn -0.0000 0.9750 0.2222 +vn 0.0000 0.9933 0.1153 +vn 0.0000 0.9999 -0.0132 +vn 0.0000 -0.9883 0.1525 +vn -0.0000 -0.9967 0.0806 +vn 0.0000 -1.0000 -0.0033 +vn 1.0000 0.0000 0.0005 +vn -0.0000 -0.9975 -0.0712 +vn -0.0000 0.9930 -0.1178 +vn 0.0000 0.9245 -0.3812 +vn 0.9899 -0.0106 0.1411 +vn 0.9886 -0.0206 0.1492 +vn 0.9940 -0.0081 0.1088 +vn 0.9992 0.0014 0.0408 +vn 0.0000 0.9991 0.0435 +vn 0.0000 0.9999 -0.0157 +vn 0.0000 0.9952 -0.0974 +vn 1.0000 0.0000 0.0008 +vn 1.0000 0.0001 0.0008 +vn 0.0000 0.9844 -0.1761 +vn 0.0000 -0.9931 -0.1172 +vn -0.0000 -0.9336 -0.3583 +vn -0.0000 -0.9908 0.1354 +vn -0.0000 -0.9546 0.2979 +vn 0.9775 0.0173 0.2103 +vn 0.9795 0.0270 0.1995 +vn 0.9970 0.0099 0.0765 +vn 0.9975 0.0084 -0.0695 +vn 0.0131 0.9904 0.1379 +vn 0.0193 0.9893 0.1446 +vn 0.0054 0.9998 0.0208 +vn 0.0145 0.9943 -0.1054 +vn 0.0068 -0.9974 0.0723 +vn 0.0093 -0.9976 0.0692 +vn 0.0003 -0.9993 -0.0377 +vn 0.0171 -0.9867 -0.1615 +vn 0.9877 0.0093 -0.1564 +vn 0.9861 0.0189 -0.1651 +vn 0.0217 -0.9695 -0.2442 +vn 0.0567 -0.9586 -0.2792 +vn 0.0174 0.9801 -0.1977 +vn 0.0368 0.9828 -0.1810 +vn 0.0000 0.9997 0.0231 +vn 0.0000 1.0000 0.0078 +vn -0.0000 1.0000 -0.0090 +vn 0.0000 -0.9999 0.0163 +vn 0.0000 -1.0000 0.0088 +vn -0.0000 -1.0000 0.0006 +vn 0.0000 -1.0000 -0.0086 +vn 0.0000 0.9996 -0.0277 +vn 0.0000 -0.9990 0.0447 +vn 0.0000 -1.0000 0.0024 +vn 0.0000 -0.9997 0.0242 +vn 0.0000 0.9997 0.0235 +vn -0.0000 0.9998 -0.0197 +vn 0.0000 1.0000 0.0025 +vn 0.0000 0.9991 -0.0431 +vn 0.0000 -0.9998 -0.0207 +vn 0.9775 -0.0173 0.2103 +vn 0.9975 -0.0084 -0.0695 +vn 0.9970 -0.0099 0.0765 +vn 0.9795 -0.0270 0.1995 +vn 0.0133 -0.9903 0.1381 +vn 0.0145 -0.9943 -0.1054 +vn 0.0054 -0.9998 0.0208 +vn 0.0193 -0.9893 0.1446 +vn 0.0069 0.9974 0.0722 +vn 0.0171 0.9867 -0.1615 +vn 0.0003 0.9993 -0.0377 +vn 0.0093 0.9976 0.0692 +vn 0.9861 -0.0189 -0.1651 +vn 0.9876 -0.0096 -0.1567 +vn 0.0567 0.9586 -0.2792 +vn 0.0216 0.9695 -0.2441 +vn 0.0368 -0.9828 -0.1810 +vn 0.0174 -0.9801 -0.1977 +vn 0.3528 -0.0598 0.9338 +vn 0.0017 0.0276 0.9996 +vn 0.3770 -0.0910 0.9217 +vn 0.1387 0.0389 0.9896 +vn 0.3550 -0.0334 0.9343 +vn 0.1631 -0.0421 0.9857 +vn 0.2549 0.0104 0.9669 +vn 0.3647 -0.2473 0.8977 +vn 0.0711 0.5172 0.8529 +vn 0.0249 0.2768 0.9606 +vn -0.0102 0.0537 0.9985 +vn -0.0044 0.0653 0.9979 +vn -0.0038 -0.1373 0.9905 +vn -0.0308 -0.2490 0.9680 +vn -0.3871 0.0550 0.9204 +vn -0.2843 0.1402 0.9484 +vn -0.4437 0.0055 0.8962 +vn -0.1473 0.0905 0.9850 +vn -0.3197 0.0541 0.9460 +vn -0.1579 0.0228 0.9872 +vn -0.1891 0.1071 0.9761 +vn -0.3253 -0.0761 0.9425 +vn 0.0250 0.2768 0.9606 +vn 0.0000 -0.2425 -0.9701 +vn 0.0000 0.0272 -0.9996 +vn 0.0000 -0.4107 -0.9118 +vn 0.0000 0.9863 -0.1651 +vn -0.0000 0.8622 -0.5065 +vn 0.0000 0.9393 -0.3430 +vn 0.0000 0.9863 -0.1650 +vn 0.0000 0.7660 -0.6429 +vn 0.0000 0.0874 0.9962 +vn -0.0000 0.4943 0.8693 +vn 0.0000 0.2835 0.9590 +vn 0.0000 0.6861 0.7275 +vn 0.0000 -0.5159 -0.8567 +vn 0.0000 0.1038 -0.9946 +vn 0.0000 0.4193 -0.9078 +vn 0.0000 0.2977 -0.9546 +vn -0.0000 -0.9403 -0.3403 +vn 0.0000 -0.9818 -0.1901 +vn 0.0000 -0.8601 -0.5102 +vn 0.0000 0.9961 0.0878 +vn 0.0000 -0.3532 0.9356 +vn 0.0000 -0.2237 0.9747 +vn 0.0000 -0.4462 0.8949 +vn 0.0000 -0.7319 -0.6815 +vn 0.0000 -0.5142 0.8577 +vn 0.0000 0.4988 -0.8667 +vn 0.0000 -0.6064 0.7951 +vn 0.3438 0.4626 0.8172 +vn 0.5340 0.2646 0.8030 +vn 0.6157 0.3476 0.7072 +vn 0.3741 0.5881 0.7171 +vn 0.6119 -0.0136 0.7908 +vn 0.7190 0.0309 0.6943 +vn 0.5272 -0.2930 0.7976 +vn 0.6594 -0.2893 0.6939 +vn 0.3119 -0.4771 0.8217 +vn 0.4502 -0.5390 0.7119 +vn 0.0480 -0.5346 0.8437 +vn 0.1390 -0.6606 0.7378 +vn -0.2100 -0.4833 0.8499 +vn -0.2027 -0.6262 0.7528 +vn -0.4340 -0.3334 0.8369 +vn -0.4965 -0.4441 0.7458 +vn -0.5774 -0.0880 0.8117 +vn -0.6745 -0.1556 0.7217 +vn -0.5734 0.2049 0.7932 +vn -0.6951 0.1728 0.6979 +vn -0.4111 0.4446 0.7958 +vn -0.5541 0.4653 0.6902 +vn -0.1614 0.5630 0.8105 +vn -0.2852 0.6549 0.6999 +vn 0.1019 0.5627 0.8204 +vn 0.0495 0.6988 0.7136 +vn 0.7486 0.4287 0.5057 +vn 0.4500 0.7329 0.5103 +vn 0.8644 0.0403 0.5013 +vn 0.7926 -0.3412 0.5053 +vn 0.5555 -0.6486 0.5202 +vn 0.1876 -0.8203 0.5403 +vn -0.2401 -0.7979 0.5529 +vn -0.6108 -0.5714 0.5481 +vn -0.8216 -0.2105 0.5297 +vn -0.8397 0.1845 0.5107 +vn -0.6801 0.5353 0.5010 +vn -0.8397 0.1844 0.5107 +vn -0.3712 0.7811 0.5021 +vn 0.0374 0.8604 0.5082 +vn 0.8100 0.4741 0.3451 +vn 0.4800 0.8053 0.3479 +vn 0.9379 0.0501 0.3431 +vn 0.8632 -0.3654 0.3484 +vn 0.6112 -0.7036 0.3626 +vn 0.2150 -0.8993 0.3810 +vn -0.2528 -0.8842 0.3927 +vn -0.6619 -0.6408 0.3888 +vn -0.8946 -0.2471 0.3723 +vn -0.9169 0.1832 0.3546 +vn -0.7485 0.5668 0.3441 +vn -0.4184 0.8411 0.3427 +vn 0.0255 0.9378 0.3463 +vn 0.8345 0.5050 0.2205 +vn 0.4847 0.8463 0.2211 +vn 0.9731 0.0642 0.2215 +vn 0.9009 -0.3691 0.2281 +vn 0.6456 -0.7247 0.2410 +vn 0.2386 -0.9366 0.2567 +vn -0.2484 -0.9312 0.2668 +vn -0.6795 -0.6845 0.2641 +vn -0.9277 -0.2767 0.2504 +vn -0.9568 0.1713 0.2348 +vn -0.7885 0.5728 0.2240 +vn -0.9568 0.1713 0.2349 +vn -0.4518 0.8646 0.2199 +vn 0.0077 0.9754 0.2204 +vn 0.8001 0.5784 0.1590 +vn 0.4154 0.8957 0.1587 +vn 0.9770 0.1396 0.1612 +vn 0.9371 -0.3060 0.1679 +vn 0.7076 -0.6834 0.1796 +vn 0.3182 -0.9280 0.1937 +vn -0.1699 -0.9641 0.2039 +vn -0.6270 -0.7520 0.2035 +vn -0.9141 -0.3568 0.1927 +vn -0.9791 0.0975 0.1783 +vn -0.8400 0.5163 0.1668 +vn -0.5257 0.8354 0.1604 +vn -0.0756 0.9844 0.1586 +vn 0.9788 -0.0473 0.1993 +vn 0.9891 -0.0496 0.1385 +vn 0.9781 -0.0471 0.2030 +vn 0.9472 -0.0422 0.3179 +vn 0.9286 -0.0397 0.3691 +vn 0.8776 -0.0338 0.4783 +vn 0.8425 -0.0302 0.5378 +vn 0.7494 -0.0216 0.6617 +vn 0.6825 -0.0160 0.7307 +vn 0.5000 -0.0023 0.8661 +vn 0.3791 0.0060 0.9253 +vn 0.1005 0.0230 0.9947 +vn -0.0574 0.0317 0.9978 +vn -0.3445 0.0455 0.9377 +vn -0.4744 0.0508 0.8788 +vn -0.6660 0.0573 0.7438 +vn -0.7379 0.0592 0.6723 +vn -0.8362 0.0608 0.5451 +vn -0.8714 0.0611 0.4867 +vn -0.9230 0.0608 0.3799 +vn -0.9422 0.0604 0.3297 +vn -0.9708 0.0592 0.2326 +vn -0.9805 0.0584 0.1878 +vn -0.9879 0.0576 0.1440 +vn 0.0099 0.9970 -0.0762 +vn -0.0186 0.9969 -0.0760 +vn -0.0126 -0.9993 -0.0349 +vn 0.0152 -0.9993 -0.0337 +vn -0.3345 0.9014 0.2751 +vn -0.7364 0.6150 0.2820 +vn -0.6166 0.7364 0.2784 +vn -0.1702 0.9468 0.2733 +vn -0.9416 0.1660 0.2929 +vn -0.8985 0.3312 0.2882 +vn -0.8949 -0.3260 0.3048 +vn -0.9402 -0.1613 0.3001 +vn -0.6079 -0.7291 0.3146 +vn -0.7297 -0.6089 0.3110 +vn -0.1572 -0.9344 0.3196 +vn -0.3230 -0.8914 0.3179 +vn 0.3359 -0.8865 0.3184 +vn 0.1709 -0.9323 0.3188 +vn 0.7384 -0.5981 0.3114 +vn 0.6186 -0.7204 0.3137 +vn 0.9423 -0.1476 0.3005 +vn 0.8998 -0.3131 0.3038 +vn 0.8935 0.3441 0.2886 +vn 0.9394 0.1796 0.2919 +vn 0.6058 0.7451 0.2789 +vn 0.7277 0.6257 0.2811 +vn 0.1565 0.9489 0.2739 +vn 0.3216 0.9063 0.2743 +vn 0.0774 0.7367 0.6717 +vn 0.3231 0.4857 0.8123 +vn 0.2029 0.9207 0.3333 +vn 0.2914 0.9468 0.1366 +vn 0.0097 0.1705 0.9853 +vn 0.2824 -0.0630 0.9572 +vn 0.1184 -0.4871 0.8653 +vn 0.3851 -0.5706 0.7253 +vn 0.3249 -0.8516 0.4112 +vn 0.5615 -0.7726 0.2964 +vn 0.5363 -0.8382 -0.0988 +vn 0.7458 -0.6568 -0.1110 +vn 0.7022 -0.4967 -0.5101 +vn 0.8840 -0.2825 -0.3724 +vn 0.7392 0.1168 -0.6633 +vn 0.8954 0.2447 -0.3721 +vn 0.5663 0.7209 -0.3994 +vn 0.4706 0.8521 -0.2289 +vn 0.8247 0.3888 0.4108 +vn 0.8563 0.1546 0.4928 +vn 0.8972 0.2943 0.3294 +vn 0.8117 0.0127 0.5839 +vn 0.9617 -0.2007 0.1865 +vn 0.9924 -0.0464 0.1136 +vn 0.9394 0.3023 0.1615 +vn 0.9713 0.2133 0.1053 +vn -0.0766 0.9970 0.0072 +vn -0.2523 0.9049 0.3428 +vn -0.2050 0.9462 0.2502 +vn 0.0013 0.9908 -0.1352 +vn -0.2732 0.6034 0.7492 +vn -0.3778 0.7048 0.6005 +vn -0.0087 0.5295 0.8483 +vn -0.2503 0.4688 0.8471 +vn -0.2336 -0.3651 0.9012 +vn -0.1908 -0.1204 0.9742 +vn -0.2555 -0.2786 0.9258 +vn -0.1622 -0.3211 0.9330 +vn 0.0599 0.1966 0.9786 +vn -0.1244 0.0456 0.9912 +vn 0.0401 -0.7825 0.6214 +vn -0.0064 -0.9585 0.2849 +vn 0.0645 -0.8837 0.4635 +vn 0.3382 -0.5943 0.7297 +vn 0.2616 -0.9505 -0.1677 +vn 0.1344 -0.9906 -0.0270 +vn 0.4711 -0.8165 -0.3337 +vn 0.4308 -0.8482 -0.3081 +vn 0.4317 -0.5413 -0.7216 +vn 0.4695 -0.6526 -0.5947 +vn 0.4794 -0.0379 -0.8768 +vn 0.4763 -0.2520 -0.8424 +vn 0.3818 0.5102 -0.7707 +vn 0.4379 0.3035 -0.8462 +vn 0.1436 0.9111 -0.3864 +vn 0.2181 0.8305 -0.5126 +vn 0.9223 -0.0450 0.3840 +vn 0.9219 -0.0452 0.3847 +vn 0.9166 -0.0438 0.3974 +vn 0.9299 -0.0502 0.3642 +vn 0.9610 -0.1054 0.2555 +vn 0.9182 -0.0691 0.3901 +vn 0.9842 -0.0688 0.1634 +vn 0.9810 -0.0530 0.1865 +vn 0.9022 -0.0803 0.4239 +vn 0.9375 -0.0115 0.3477 +vn 0.9587 0.1377 0.2491 +vn 0.9169 -0.0654 0.3938 +vn 0.9312 0.2688 0.2461 +vn 0.9309 0.2609 0.2556 +vn 0.9210 0.0656 0.3840 +vn 0.9349 0.1663 0.3136 +vn 0.8449 0.1598 0.5105 +vn 0.8607 0.1468 0.4875 +vn 0.9464 0.1072 0.3046 +vn 0.9147 0.1207 0.3856 +vn 0.9340 -0.0896 0.3459 +vn 0.9399 -0.0040 0.3413 +vn 0.9346 -0.0948 0.3428 +vn 0.9249 -0.1436 0.3521 +vn 0.9337 -0.0509 0.3545 +vn 0.9296 -0.0493 0.3653 +vn 0.0885 -0.9956 -0.0294 +vn 0.2517 -0.9055 -0.3416 +vn 0.2079 -0.9441 -0.2559 +vn 0.0089 -0.9931 0.1169 +vn 0.2857 -0.5488 -0.7856 +vn 0.3693 -0.7243 -0.5822 +vn 0.0595 -0.2999 -0.9521 +vn 0.2646 -0.3958 -0.8794 +vn 0.2960 0.4503 -0.8424 +vn 0.4699 0.0969 -0.8774 +vn 0.3813 0.2711 -0.8838 +vn 0.1814 0.4897 -0.8528 +vn 0.4783 -0.0138 -0.8781 +vn 0.2543 0.7847 -0.5653 +vn 0.0613 0.9507 -0.3039 +vn 0.1603 0.8658 -0.4740 +vn 0.2504 0.7855 -0.5660 +vn -0.2439 0.9605 0.1340 +vn -0.0961 0.9951 -0.0247 +vn -0.4621 0.8191 0.3400 +vn -0.4571 0.8256 0.3309 +vn -0.4248 0.5677 0.7052 +vn -0.4534 0.7113 0.5372 +vn -0.4782 0.0100 0.8782 +vn -0.4798 0.1843 0.8578 +vn -0.3973 -0.4622 0.7928 +vn -0.4412 -0.2873 0.8502 +vn -0.1578 -0.8980 0.4107 +vn -0.2503 -0.7856 0.5658 +vn 0.9990 -0.0000 0.0458 +vn 0.9958 -0.0001 0.0913 +vn 0.9978 -0.0001 0.0670 +vn 0.9731 0.0956 -0.2095 +vn 0.9988 -0.0001 -0.0481 +vn 0.9210 0.2690 -0.2817 +vn 0.9220 0.1802 -0.3428 +vn 0.8879 0.4017 -0.2242 +vn 0.8949 0.3341 -0.2958 +vn 0.8879 0.4017 -0.2243 +vn 0.9966 0.0798 -0.0211 +vn 0.9761 0.1949 -0.0964 +vn 0.0000 -0.9993 0.0362 +vn 0.0000 -0.9998 0.0196 +vn -0.0000 -1.0000 0.0011 +vn -0.0000 0.9998 0.0204 +vn 0.0000 0.9991 0.0430 +vn -0.0000 -0.9953 -0.0968 +vn -0.0000 -0.9805 -0.1964 +vn -0.0000 0.9920 0.1266 +vn -0.0000 0.9821 0.1881 +vn -0.0000 -0.9162 -0.4007 +vn -0.0000 -0.8668 -0.4986 +vn -0.0000 0.9385 0.3453 +vn -0.0000 0.8929 0.4502 +vn -0.0000 -0.7409 -0.6716 +vn 0.0000 -0.6625 -0.7490 +vn 0.0000 0.7459 0.6660 +vn 0.0000 0.6530 0.7574 +vn 0.0205 -0.1624 -0.9865 +vn 0.0003 -0.1597 -0.9872 +vn 0.0065 0.1851 0.9827 +vn -0.0000 0.1935 0.9811 +vn 0.0166 0.0000 0.9999 +vn 0.0083 -0.0000 1.0000 +vn -0.0000 0.1936 0.9811 +vn 0.0212 -0.1624 -0.9865 +vn 0.0504 0.0000 -0.9987 +vn 0.0252 -0.0000 -0.9997 +vn -0.0000 -0.1596 -0.9872 +vn 0.9977 0.0001 0.0671 +vn 0.9958 0.0001 0.0913 +vn 0.9988 0.0001 -0.0481 +vn 0.9731 -0.0956 -0.2095 +vn 0.9235 -0.1804 -0.3384 +vn 0.9263 -0.2780 -0.2545 +vn 0.8992 -0.3535 -0.2578 +vn 0.8790 -0.4349 -0.1957 +vn 0.9749 -0.2048 -0.0874 +vn 0.9965 -0.0810 -0.0215 +vn 0.0000 0.9993 0.0362 +vn 0.0000 1.0000 0.0011 +vn 0.0000 0.9998 0.0196 +vn -0.0000 -0.9998 0.0204 +vn 0.0000 -0.9991 0.0430 +vn -0.0000 0.9805 -0.1964 +vn -0.0000 0.9953 -0.0968 +vn -0.0000 -0.9920 0.1266 +vn -0.0000 -0.9821 0.1881 +vn 0.0000 0.8470 -0.5315 +vn 0.0000 0.9056 -0.4242 +vn -0.0000 -0.9303 0.3668 +vn 0.0000 -0.8759 0.4825 +vn -0.0000 0.6683 -0.7439 +vn -0.0000 0.7349 -0.6781 +vn -0.0000 -0.7408 0.6717 +vn -0.0000 -0.6571 0.7538 +vn -0.0001 0.6683 -0.7439 +vn -0.0000 0.1473 -0.9891 +vn 0.0210 0.1521 -0.9881 +vn 0.0066 -0.1755 0.9845 +vn -0.0000 -0.1832 0.9831 +vn 0.0066 -0.1755 0.9844 +vn 0.9965 -0.0810 -0.0214 +vn 0.0000 0.1474 -0.9891 +vn -0.8829 0.3876 -0.2650 +vn -0.9131 0.4009 -0.0741 +vn -0.9129 0.4008 0.0766 +vn -0.7771 -0.5708 -0.2650 +vn -0.8037 -0.5903 -0.0741 +vn -0.8036 -0.5902 0.0766 +vn 0.1058 -0.9584 -0.2650 +vn 0.1094 -0.9912 -0.0741 +vn 0.1094 -0.9910 0.0766 +vn 0.8829 -0.3876 -0.2650 +vn 0.9131 -0.4009 -0.0741 +vn 0.9129 -0.4008 0.0766 +vn 0.7771 0.5708 -0.2650 +vn 0.8037 0.5903 -0.0741 +vn 0.8036 0.5902 0.0766 +vn -0.1058 0.9584 -0.2650 +vn -0.1094 0.9912 -0.0741 +vn -0.1094 0.9910 0.0766 +vn 0.8741 -0.3838 0.2977 +vn 0.1047 -0.9489 0.2977 +vn -0.7694 -0.5651 0.2977 +vn -0.8741 0.3838 0.2977 +vn -0.1047 0.9489 0.2977 +vn 0.7694 0.5651 0.2977 +vn -0.4522 -0.8790 -0.1513 +vn -0.4570 -0.8883 -0.0458 +vn -0.4570 -0.8882 0.0476 +vn 0.5351 -0.8311 -0.1513 +vn 0.5408 -0.8399 -0.0458 +vn 0.5407 -0.8398 0.0476 +vn 0.9873 0.0479 -0.1513 +vn 0.9978 0.0484 -0.0458 +vn 0.9977 0.0484 0.0476 +vn 0.4522 0.8790 -0.1513 +vn 0.4570 0.8883 -0.0458 +vn 0.4570 0.8882 0.0476 +vn -0.5351 0.8311 -0.1513 +vn -0.5408 0.8399 -0.0458 +vn -0.5407 0.8398 0.0476 +vn -0.9873 -0.0479 -0.1513 +vn -0.9978 -0.0484 -0.0458 +vn -0.9977 -0.0484 0.0476 +vn 0.4507 0.8761 0.1711 +vn 0.9841 0.0477 0.1711 +vn 0.5334 -0.8284 0.1711 +vn -0.4507 -0.8761 0.1711 +vn -0.9841 -0.0477 0.1711 +vn -0.5334 0.8284 0.1711 +vn 0.0603 -0.2958 0.9533 +vn -0.2055 -0.2381 0.9492 +vn -0.2064 -0.2547 0.9447 +vn 0.0544 -0.3166 0.9470 +vn 0.3171 -0.3152 0.8945 +vn 0.3230 -0.2944 0.8994 +vn -0.3558 0.5538 0.7528 +vn -0.3255 0.5939 0.7358 +vn -0.3371 0.5920 0.7320 +vn -0.3785 0.5126 0.7707 +vn -0.4034 0.4450 0.7995 +vn -0.4046 0.4445 0.7992 +vn -0.3803 -0.0847 0.9210 +vn -0.4434 0.2576 0.8585 +vn -0.4360 0.1198 0.8919 +vn -0.3119 -0.1575 0.9370 +vn -0.2541 -0.2410 0.9367 +vn -0.2476 -0.2176 0.9441 +vn 0.1913 0.7524 0.6304 +vn 0.2943 0.7596 0.5800 +vn 0.2964 0.7434 0.5996 +vn 0.1917 0.7436 0.6405 +vn 0.0821 0.7522 0.6538 +vn 0.0818 0.7504 0.6559 +vn 0.7132 -0.0262 0.7005 +vn 0.7478 -0.0838 0.6586 +vn 0.7111 -0.0697 0.6996 +vn 0.6880 -0.0174 0.7255 +vn 0.7130 0.0736 0.6973 +vn 0.7151 0.0688 0.6956 +vn 0.6822 0.4161 0.6012 +vn 0.7275 0.2696 0.6309 +vn 0.7113 0.3379 0.6164 +vn 0.8424 0.4615 0.2781 +vn 0.6699 0.4610 0.5820 +vn 0.8378 0.5311 0.1268 +vn 0.2226 0.9591 -0.1750 +vn 0.3593 0.9173 -0.1716 +vn 0.3546 0.9169 -0.1833 +vn 0.0764 0.9900 -0.1187 +vn -0.0749 0.9947 -0.0705 +vn -0.0351 0.9993 0.0114 +vn 0.9047 -0.4254 0.0250 +vn 0.8906 -0.4539 0.0276 +vn 0.8208 -0.5061 -0.2650 +vn 0.7465 -0.4517 -0.4886 +vn 0.9678 -0.2509 -0.0171 +vn 0.9679 -0.2508 -0.0175 +vn 0.9912 0.0878 -0.0987 +vn 0.9678 0.2183 -0.1249 +vn 0.8705 0.4640 -0.1641 +vn 0.8442 0.5085 -0.1698 +vn -0.2021 0.5475 -0.8120 +vn -0.2200 0.5786 -0.7854 +vn -0.0659 0.5226 -0.8500 +vn -0.0562 0.4786 -0.8762 +vn 0.1026 0.4953 -0.8626 +vn 0.0860 0.4827 -0.8715 +vn 0.5925 -0.4698 -0.6544 +vn 0.5577 -0.3999 -0.7274 +vn 0.5656 -0.3850 -0.7293 +vn 0.5700 -0.3817 -0.7276 +vn 0.5404 0.0934 -0.8362 +vn 0.6005 -0.1264 -0.7896 +vn 0.5760 -0.0212 -0.8172 +vn 0.5303 0.1274 -0.8382 +vn 0.5226 0.1579 -0.8378 +vn 0.5342 0.1581 -0.8305 +vn -0.2085 -0.7243 -0.6572 +vn -0.4707 -0.6550 -0.5912 +vn -0.4780 -0.6597 -0.5799 +vn -0.1963 -0.7460 -0.6363 +vn 0.0811 -0.7287 -0.6800 +vn 0.0622 -0.7117 -0.6997 +vn -0.6063 0.1544 -0.7801 +vn -0.5169 0.2147 -0.8287 +vn -0.5285 0.2645 -0.8067 +vn -0.6362 0.1711 -0.7523 +vn -0.6936 0.0000 -0.7204 +vn -0.6948 -0.0003 -0.7192 +vn -0.6393 -0.5077 -0.5775 +vn -0.7240 -0.2166 -0.6550 +vn -0.7118 -0.3419 -0.6136 +vn -0.5717 -0.5843 -0.5760 +vn -0.5152 -0.6411 -0.5688 +vn -0.4935 -0.6455 -0.5829 +vn 0.2890 -0.9433 0.1635 +vn -0.8523 0.5229 0.0122 +vn -0.9022 0.4295 0.0400 +vn -0.7980 0.6025 -0.0133 +vn -0.9362 0.3458 0.0634 +vn -0.8524 -0.4699 0.2295 +vn -0.7768 -0.5811 0.2429 +vn -0.9595 -0.2088 0.1893 +vn -0.9889 -0.0031 0.1482 +vn -0.7002 -0.6687 0.2503 +vn -0.7001 -0.6687 0.2503 +vn -0.4309 0.3981 0.8099 +vn -0.4337 0.3959 0.8094 +vn -0.9696 0.2250 0.0966 +vn -0.9694 0.2264 0.0945 +vn -0.7217 -0.0749 -0.6881 +vn -0.7312 -0.0577 -0.6797 +vn 0.6099 -0.2201 -0.7613 +vn 0.6101 -0.2202 -0.7611 +vn 0.9967 -0.0384 -0.0716 +vn 0.9965 -0.0377 -0.0748 +vn 0.7382 0.1910 0.6469 +vn 0.7438 0.1942 0.6395 +vn -0.7860 0.6183 -0.0047 +vn -0.7885 0.6135 -0.0429 +vn -0.7913 0.6107 -0.0302 +vn -0.7736 0.6332 -0.0219 +vn -0.7859 0.6183 -0.0047 +vn -0.7369 0.6723 0.0712 +vn -0.7892 0.6137 0.0245 +vn -0.6282 0.7780 0.0069 +vn -0.6824 0.7294 0.0474 +vn -0.6386 0.7570 -0.1384 +vn -0.6834 0.7151 -0.1473 +vn -0.7806 0.6212 -0.0687 +vn -0.7605 0.6320 -0.1488 +vn -0.2800 -0.9250 0.2568 +vn -0.2794 -0.9351 0.2182 +vn -0.2395 -0.9185 0.3146 +vn -0.2485 -0.9163 0.3142 +vn -0.2800 -0.9250 0.2567 +vn -0.2905 -0.9287 0.2306 +vn -0.2645 -0.9292 0.2581 +vn -0.2338 -0.9630 0.1340 +vn -0.2997 -0.9310 0.2083 +vn -0.0749 -0.9844 0.1590 +vn -0.1456 -0.9799 0.1366 +vn -0.0695 -0.9509 0.3017 +vn -0.1295 -0.9390 0.3185 +vn 0.6662 0.2306 -0.7092 +vn 0.2308 0.0072 -0.9730 +vn 0.4019 0.0870 -0.9115 +vn 0.7622 0.2939 -0.5768 +vn -0.3132 -0.2009 -0.9282 +vn -0.1312 -0.1393 -0.9815 +vn 0.4019 0.0869 -0.9115 +vn -0.7307 -0.3096 -0.6084 +vn -0.6120 -0.2865 -0.7372 +vn -0.9350 -0.3059 -0.1797 +vn -0.8887 -0.3180 -0.3303 +vn -0.9403 -0.2130 0.2654 +vn -0.9604 -0.2539 0.1150 +vn -0.7373 -0.0400 0.6743 +vn -0.8321 -0.1071 0.5441 +vn -0.3087 0.1932 0.9313 +vn -0.4773 0.1115 0.8717 +vn 0.2304 0.3984 0.8878 +vn 0.0497 0.3385 0.9397 +vn 0.6520 0.4939 0.5753 +vn 0.5318 0.4739 0.7018 +vn 0.8623 0.4833 0.1511 +vn 0.8142 0.4967 0.3007 +vn 0.8708 0.3935 -0.2948 +vn 0.8903 0.4321 -0.1435 +vn 0.2339 0.3907 0.8903 +vn 0.6567 0.4867 0.5761 +vn 0.5364 0.4665 0.7033 +vn 0.0521 0.3303 0.9424 +vn -0.3078 0.1842 0.9335 +vn -0.4767 0.1022 0.8731 +vn -0.7366 -0.0483 0.6746 +vn -0.8307 -0.1159 0.5445 +vn -0.9382 -0.2212 0.2662 +vn -0.9581 -0.2619 0.1162 +vn -0.9328 -0.3136 -0.1777 +vn -0.8867 -0.3258 -0.3281 +vn -0.7287 -0.3181 -0.6065 +vn -0.6099 -0.2948 -0.7356 +vn -0.3097 -0.2095 -0.9274 +vn -0.1270 -0.1475 -0.9809 +vn 0.2364 -0.0003 -0.9717 +vn 0.4076 0.0797 -0.9097 +vn 0.6713 0.2235 -0.7067 +vn 0.7669 0.2865 -0.5743 +vn 0.8746 0.3862 -0.2931 +vn 0.8941 0.4248 -0.1422 +vn 0.8663 0.4759 0.1516 +vn 0.8184 0.4894 0.3010 +vn 0.2127 0.4155 0.8843 +vn 0.6379 0.5117 0.5756 +vn 0.5159 0.4914 0.7017 +vn 0.0306 0.3547 0.9345 +vn -0.3263 0.2090 0.9219 +vn -0.4940 0.1265 0.8602 +vn -0.7495 -0.0240 0.6616 +vn -0.8424 -0.0915 0.5310 +vn -0.9471 -0.1961 0.2541 +vn -0.9660 -0.2368 0.1039 +vn -0.9389 -0.2880 -0.1885 +vn -0.8920 -0.2989 -0.3391 +vn -0.7334 -0.2878 -0.6159 +vn -0.6130 -0.2643 -0.7446 +vn -0.3123 -0.1776 -0.9332 +vn -0.1291 -0.1138 -0.9851 +vn 0.2322 0.0334 -0.9721 +vn 0.4031 0.1128 -0.9082 +vn 0.6635 0.2544 -0.7036 +vn 0.2323 0.0334 -0.9721 +vn 0.7577 0.3173 -0.5704 +vn 0.8632 0.4137 -0.2895 +vn 0.8821 0.4503 -0.1380 +vn 0.8513 0.5016 0.1540 +vn 0.8020 0.5146 0.3033 +vn -0.3992 0.9135 -0.0781 +vn -0.4299 0.8972 -0.1011 +vn -0.4210 0.9024 -0.0916 +vn -0.3861 0.9195 -0.0741 +vn -0.3590 0.9305 -0.0731 +vn -0.3993 0.9135 -0.0781 +vn -0.3452 0.9354 -0.0763 +vn -0.3206 0.9431 -0.0883 +vn -0.3102 0.9457 -0.0976 +vn -0.2950 0.9481 -0.1190 +vn -0.2908 0.9477 -0.1315 +vn -0.2887 0.9446 -0.1563 +vn -0.2914 0.9416 -0.1688 +vn -0.3026 0.9338 -0.1909 +vn -0.3115 0.9288 -0.2005 +vn -0.3335 0.9181 -0.2143 +vn -0.3468 0.9122 -0.2183 +vn -0.3740 0.9011 -0.2193 +vn -0.3880 0.8960 -0.2161 +vn -0.4125 0.8878 -0.2039 +vn -0.4228 0.8851 -0.1945 +vn -0.4377 0.8823 -0.1728 +vn -0.4418 0.8827 -0.1603 +vn -0.4437 0.8859 -0.1355 +vn -0.4410 0.8890 -0.1230 +vn 0.3807 -0.9055 0.1873 +vn 0.4014 -0.8991 0.1745 +vn 0.3951 -0.9009 0.1799 +vn 0.3725 -0.9085 0.1892 +vn 0.3563 -0.9150 0.1892 +vn 0.3484 -0.9185 0.1870 +vn 0.3349 -0.9250 0.1795 +vn 0.3293 -0.9281 0.1738 +vn 0.3221 -0.9330 0.1608 +vn 0.3205 -0.9348 0.1533 +vn 0.3214 -0.9368 0.1384 +vn 0.3241 -0.9369 0.1309 +vn 0.3330 -0.9355 0.1180 +vn 0.3393 -0.9339 0.1127 +vn 0.3537 -0.9294 0.1053 +vn 0.3619 -0.9265 0.1034 +vn 0.3780 -0.9200 0.1035 +vn 0.3859 -0.9165 0.1056 +vn 0.3993 -0.9098 0.1131 +vn 0.4048 -0.9067 0.1186 +vn 0.4121 -0.9016 0.1316 +vn 0.4137 -0.8997 0.1391 +vn 0.4128 -0.8977 0.1540 +vn 0.4101 -0.8976 0.1615 +vn -0.4264 0.9003 -0.0871 +vn -0.3883 0.9190 -0.0686 +vn -0.4028 0.9124 -0.0725 +vn -0.4364 0.8945 -0.0972 +vn -0.3441 0.9363 -0.0707 +vn -0.3592 0.9308 -0.0673 +vn -0.3057 0.9476 -0.0930 +vn -0.3174 0.9446 -0.0831 +vn -0.2834 0.9502 -0.1293 +vn -0.2885 0.9505 -0.1157 +vn -0.2834 0.9438 -0.1703 +vn -0.2804 0.9470 -0.1564 +vn -0.3056 0.9298 -0.2049 +vn -0.2954 0.9353 -0.1946 +vn -0.3443 0.9118 -0.2238 +vn -0.3296 0.9182 -0.2198 +vn -0.3887 0.8943 -0.2216 +vn -0.3736 0.8999 -0.2251 +vn -0.4269 0.8821 -0.1990 +vn -0.4154 0.8853 -0.2090 +vn -0.4487 0.8788 -0.1622 +vn -0.4438 0.8787 -0.1760 +vn -0.4485 0.8855 -0.1212 +vn -0.4515 0.8820 -0.1351 +vn 0.9339 -0.2815 -0.2205 +vn 0.3630 -0.9318 0.0000 +vn 0.5986 -0.8011 0.0000 +vn 0.1148 -0.9934 0.0000 +vn -0.0338 0.9994 0.0000 +vn 0.1090 0.9940 0.0000 +vn -0.1826 0.9832 0.0000 +vn 0.9977 0.0671 0.0000 +vn 0.9970 0.0771 -0.0000 +vn 0.9971 0.0762 -0.0000 +vn -0.9949 -0.1004 0.0000 +vn -0.9947 -0.1031 0.0000 +vn -0.9952 -0.0984 -0.0000 +vn -0.9755 -0.2202 0.0000 +vn -0.9922 -0.1250 -0.0000 +vn -0.9918 -0.1281 -0.0000 +vn 0.9842 0.1768 -0.0000 +vn 0.9858 0.1677 -0.0000 +vn 0.9785 0.2063 0.0000 +vn -0.1008 -0.9949 0.0000 +vn -0.3278 0.9448 0.0000 +vn -0.0019 0.0017 1.0000 +vn -0.9946 -0.1037 -0.0000 +vn -0.9946 -0.1035 -0.0000 +vn 0.9940 0.1097 -0.0000 +vn 0.9905 0.1377 0.0000 +vn -0.1955 -0.9807 0.0000 +vn 0.0907 -0.9959 0.0000 +vn 0.0906 -0.9959 -0.0000 +vn -0.4029 0.9153 0.0000 +vn -0.1309 0.9914 0.0000 +vn -0.2638 0.9646 0.0000 +vn 0.9953 0.0968 0.0000 +vn 0.9945 0.1043 0.0000 +vn 0.9946 0.1038 0.0000 +vn -0.9940 -0.1096 0.0000 +vn -0.9936 -0.1130 0.0000 +vn -0.0106 0.9999 0.0000 +vn 0.3383 -0.9410 0.0000 +vn -0.0032 0.0341 0.9994 +vn 0.0069 -0.0323 0.9995 +vn -0.9940 -0.1095 -0.0000 +vn -0.9944 -0.1053 0.0000 +vn 0.9946 0.1035 0.0000 +vn 0.9948 0.1015 -0.0000 +vn -0.9805 -0.1967 0.0000 +vn -0.9935 -0.1135 0.0000 +vn -0.9900 -0.1410 -0.0000 +vn 0.9891 0.1473 0.0000 +vn 0.9935 0.1135 -0.0000 +vn 0.9752 0.2212 0.0000 +vn -0.2700 0.9629 -0.0000 +vn -0.1541 0.9880 0.0000 +vn -0.3939 0.9192 -0.0000 +vn 1.0000 -0.0019 -0.0000 +vn 0.9952 0.0974 0.0000 +vn 0.9957 0.0927 0.0000 +vn -0.9849 -0.1730 0.0000 +vn -0.6818 -0.7315 -0.0000 +vn -0.8163 -0.5777 -0.0000 +vn -0.9950 -0.1002 -0.0000 +vn -0.9886 -0.1503 0.0000 +vn -0.9936 -0.1133 -0.0000 +vn -0.9934 -0.1145 0.0000 +vn 0.9903 0.1389 -0.0000 +vn 0.9935 0.1139 0.0000 +vn 0.9745 0.2244 -0.0000 +vn -0.5030 -0.8643 -0.0000 +vn -0.5195 0.8545 -0.0000 +vn -0.9960 -0.0894 -0.0000 +vn -0.9960 -0.0895 -0.0000 +vn 0.9939 0.1102 -0.0000 +vn 0.9942 0.1076 0.0000 +vn 0.0100 0.0028 0.9999 +vn -0.8167 0.4972 -0.2928 +vn -0.0604 0.8269 -0.5590 +vn -0.9187 -0.3486 -0.1857 +vn -0.2785 -0.9020 -0.3299 +vn 0.5015 -0.6229 -0.6004 +vn 0.6516 -0.2777 -0.7059 +vn 0.4813 0.6465 -0.5919 +vn -0.3101 0.9485 -0.0651 +vn -0.2598 0.9635 0.0651 +vn -0.9536 0.2387 0.1836 +vn -0.9128 0.2566 0.3178 +vn -0.6673 -0.7219 0.1835 +vn -0.6234 -0.7144 0.3177 +vn 0.2598 -0.9635 -0.0651 +vn 0.3101 -0.9485 0.0651 +vn 0.9128 -0.2566 -0.3177 +vn 0.9536 -0.2387 -0.1835 +vn 0.6234 0.7144 -0.3178 +vn 0.6673 0.7218 -0.1836 +vn -0.4022 0.7251 0.5590 +vn 0.4112 0.8632 0.2928 +vn -0.7567 0.2775 0.5919 +vn -0.0786 -0.7958 0.6004 +vn -0.3932 -0.5891 0.7059 +vn 0.7269 -0.6023 0.3299 +vn 0.9596 0.2113 0.1857 +vn 0.0116 0.9981 -0.0611 +vn 0.4569 0.6224 -0.6356 +vn -0.7070 0.6702 0.2257 +vn -0.9964 -0.0807 -0.0275 +vn -0.6112 -0.5212 -0.5956 +vn -0.3147 -0.4650 -0.8275 +vn 0.5012 0.0897 -0.8607 +vn 0.6776 0.7252 -0.1227 +vn 0.7621 0.6469 -0.0275 +vn -0.0580 0.8803 0.4708 +vn 0.0318 0.8148 0.5789 +vn -0.8132 0.1730 0.5557 +vn -0.7376 0.0945 0.6686 +vn -0.7621 -0.6469 0.0275 +vn -0.6776 -0.7252 0.1227 +vn -0.0318 -0.8148 -0.5789 +vn 0.0580 -0.8803 -0.4709 +vn 0.7376 -0.0945 -0.6686 +vn 0.8132 -0.1730 -0.5557 +vn 0.7106 0.4944 0.5007 +vn 0.9984 -0.0353 -0.0442 +vn 0.2284 0.5977 0.7685 +vn -0.4668 -0.5076 0.7242 +vn -0.3617 -0.1763 0.9155 +vn -0.1166 -0.9827 0.1440 +vn 0.6030 -0.7683 -0.2147 +vn 0.4781 0.8426 -0.2479 +vn -0.0949 0.9693 -0.2270 +vn 0.1098 0.9665 -0.2320 +vn 0.6425 0.7206 -0.2605 +vn -0.6341 0.7376 -0.2320 +vn -0.4662 0.8548 -0.2277 +vn -0.9364 0.2347 -0.2611 +vn -0.8687 0.4280 -0.2494 +vn -0.8859 -0.3507 -0.3036 +vn -0.9449 -0.1540 -0.2889 +vn -0.4986 -0.7959 -0.3435 +vn -0.6630 -0.6711 -0.3316 +vn 0.0799 -0.9275 -0.3652 +vn -0.1275 -0.9238 -0.3610 +vn 0.6256 -0.6921 -0.3600 +vn 0.4560 -0.8115 -0.3654 +vn 0.9263 -0.1819 -0.3300 +vn 0.8602 -0.3773 -0.3431 +vn 0.8684 0.4042 -0.2872 +vn 0.9300 0.2084 -0.3029 +vn 0.3266 0.9298 -0.1695 +vn -0.4266 0.8884 -0.1695 +vn -0.3001 0.9484 -0.1026 +vn 0.4584 0.8828 -0.1026 +vn -0.9298 0.3266 -0.1695 +vn -0.8828 0.4584 -0.1026 +vn -0.8884 -0.4266 -0.1695 +vn -0.9484 -0.3001 -0.1026 +vn -0.3266 -0.9298 -0.1695 +vn -0.4584 -0.8828 -0.1026 +vn 0.4266 -0.8884 -0.1695 +vn 0.3001 -0.9484 -0.1026 +vn 0.9299 -0.3266 -0.1695 +vn 0.8828 -0.4584 -0.1026 +vn 0.9298 -0.3266 -0.1695 +vn 0.8884 0.4266 -0.1695 +vn 0.9484 0.3001 -0.1026 +vn -0.4712 -0.8714 0.1369 +vn 0.4310 -0.8896 0.1515 +vn -0.2905 -0.9383 0.1879 +vn -0.8714 0.4712 0.1368 +vn -0.9383 0.2905 0.1879 +vn 0.8080 -0.5880 -0.0370 +vn 0.1555 -0.9871 -0.0370 +vn -0.5881 -0.8080 -0.0370 +vn -0.9871 -0.1555 -0.0370 +vn -0.8080 0.5881 -0.0370 +vn -0.1555 0.9871 -0.0370 +vn 0.5881 0.8080 -0.0370 +vn 0.9871 0.1555 -0.0370 +vn 0.8080 -0.5881 -0.0371 +vn 0.0935 -0.9956 0.0000 +vn 0.1121 -0.9937 -0.0000 +vn 0.1131 -0.9936 -0.0000 +vn 0.1137 0.0167 0.9934 +vn 0.0199 0.0058 0.9998 +vn 0.0273 -0.0016 0.9996 +vn 0.1220 0.0039 0.9925 +vn 0.0435 0.9991 0.0000 +vn -0.0736 0.9973 -0.0000 +vn -0.0155 0.9999 -0.0000 +vn 0.9528 0.3037 0.0000 +vn 0.9528 0.3036 0.0000 +vn -0.2828 0.9592 -0.0000 +vn -0.3238 0.9461 0.0000 +vn -0.2376 0.9714 -0.0000 +vn 0.1604 -0.9871 0.0000 +vn 0.1408 -0.9900 -0.0000 +vn 0.1778 -0.9841 0.0000 +vn -0.0457 -0.0121 0.9989 +vn -0.0460 -0.0116 0.9989 +vn -0.0455 -0.0130 0.9989 +vn -0.0460 -0.0147 0.9988 +vn -0.1670 0.9860 -0.0000 +vn -0.1512 0.9885 -0.0000 +vn 0.1285 -0.9917 -0.0000 +vn 0.1219 -0.9925 0.0000 +vn -0.0727 -0.0215 0.9971 +vn -0.0593 -0.0166 0.9981 +vn 0.4978 0.8058 0.3208 +vn -0.0709 0.9445 0.3208 +vn 0.1333 0.9379 0.3203 +vn 0.6591 0.6804 0.3203 +vn -0.6125 0.7224 0.3208 +vn -0.4435 0.8371 0.3203 +vn -0.9202 0.2244 0.3208 +vn -0.8508 0.4166 0.3203 +vn -0.8764 -0.3593 0.3208 +vn -0.9332 -0.1631 0.3203 +vn -0.4978 -0.8058 0.3208 +vn -0.6591 -0.6805 0.3203 +vn 0.0709 -0.9445 0.3208 +vn -0.1333 -0.9379 0.3203 +vn 0.6125 -0.7224 0.3208 +vn 0.4435 -0.8371 0.3203 +vn 0.9202 -0.2244 0.3208 +vn 0.8508 -0.4166 0.3203 +vn 0.8764 0.3593 0.3208 +vn 0.9332 0.1631 0.3203 +vn 0.6591 0.6805 0.3203 +vn -0.0910 -0.9958 0.0000 +vn -0.0716 -0.9974 0.0000 +vn -0.0726 -0.9974 0.0000 +vn 0.2411 0.0011 0.9705 +vn 0.0941 0.0042 0.9956 +vn 0.1093 -0.0019 0.9940 +vn 0.2440 -0.0059 0.9698 +vn 0.2943 0.9557 -0.0000 +vn 0.1307 0.9914 0.0000 +vn 0.2270 0.9739 -0.0000 +vn -0.2561 0.9667 -0.0000 +vn -0.2207 0.9753 -0.0000 +vn 0.1891 -0.9820 -0.0000 +vn 0.1645 -0.9864 -0.0000 +vn 0.2089 -0.9779 0.0000 +vn -0.1248 -0.0043 0.9922 +vn -0.0895 -0.0076 0.9960 +vn -0.1540 -0.0045 0.9881 +vn -0.1515 0.0028 0.9884 +vn -0.1168 0.9932 0.0000 +vn -0.0491 0.9988 0.0000 +vn 0.0190 -0.9998 0.0000 +vn 0.0796 -0.9968 -0.0000 +vn -0.0784 -0.0056 0.9969 +vn -0.0628 -0.0055 0.9980 +vn 0.2552 -0.9611 -0.1056 +vn 0.7714 -0.6276 -0.1056 +vn 0.6188 -0.7784 -0.1058 +vn 0.0432 -0.9934 -0.1058 +vn 0.9929 -0.0543 -0.1056 +vn 0.9581 -0.2660 -0.1058 +vn 0.8352 0.5397 -0.1056 +vn 0.9315 0.3480 -0.1058 +vn 0.3585 0.9275 -0.1056 +vn 0.5490 0.8291 -0.1058 +vn -0.2552 0.9611 -0.1056 +vn -0.0432 0.9934 -0.1058 +vn -0.7714 0.6276 -0.1056 +vn -0.6188 0.7784 -0.1058 +vn -0.9929 0.0543 -0.1056 +vn -0.9582 0.2660 -0.1058 +vn -0.8352 -0.5397 -0.1056 +vn -0.9315 -0.3480 -0.1058 +vn -0.3584 -0.9276 -0.1056 +vn -0.5490 -0.8291 -0.1058 +vn -0.3585 -0.9275 -0.1056 +vn 0.2540 -0.9523 -0.1693 +vn 0.7652 -0.6211 -0.1693 +vn 0.6141 -0.7708 -0.1697 +vn 0.0437 -0.9845 -0.1697 +vn 0.9841 -0.0527 -0.1693 +vn 0.9498 -0.2627 -0.1697 +vn 0.8272 0.5358 -0.1693 +vn 0.9228 0.3458 -0.1697 +vn 0.3543 0.9197 -0.1693 +vn 0.5433 0.8222 -0.1697 +vn -0.2540 0.9523 -0.1693 +vn -0.0437 0.9845 -0.1697 +vn -0.7652 0.6211 -0.1693 +vn -0.6141 0.7708 -0.1697 +vn -0.9841 0.0527 -0.1693 +vn -0.9498 0.2627 -0.1697 +vn -0.8272 -0.5358 -0.1693 +vn -0.9228 -0.3458 -0.1697 +vn -0.3543 -0.9197 -0.1693 +vn -0.5433 -0.8222 -0.1697 +vn 0.1412 -0.9837 -0.1115 +vn 0.6887 -0.7231 -0.0528 +vn 0.6000 -0.7501 -0.2783 +vn 0.0448 -0.9673 -0.2498 +vn 0.9812 -0.1862 -0.0506 +vn 0.9223 -0.2546 -0.2908 +vn 0.9047 0.4146 -0.0985 +vn 0.9008 0.3368 -0.2741 +vn 0.4957 0.8480 -0.1877 +vn 0.5340 0.8076 -0.2501 +vn -0.0869 0.9526 -0.2917 +vn -0.0437 0.9703 -0.2380 +vn -0.6239 0.6899 -0.3673 +vn -0.6062 0.7597 -0.2353 +vn -0.9116 0.1600 -0.3785 +vn -0.9374 0.2587 -0.2332 +vn -0.8424 -0.4345 -0.3187 +vn -0.9113 -0.3417 -0.2295 +vn -0.4429 -0.8707 -0.2140 +vn -0.5356 -0.8120 -0.2319 +vn 0.2088 -0.9765 0.0528 +vn 0.7087 -0.6814 0.1829 +vn 0.9733 -0.1397 0.1823 +vn 0.8946 0.4440 0.0497 +vn 0.5016 0.8496 -0.1632 +vn -0.0649 0.9230 -0.3793 +vn -0.5913 0.6197 -0.5160 +vn -0.8568 0.0503 -0.5132 +vn -0.7522 -0.5436 -0.3725 +vn -0.3416 -0.9267 -0.1567 +vn 0.2930 -0.9398 0.1761 +vn 0.7535 -0.5818 0.3061 +vn 0.9638 -0.0213 0.2659 +vn 0.8483 0.5246 0.0723 +vn 0.4599 0.8635 -0.2070 +vn -0.0783 0.8729 -0.4817 +vn -0.5761 0.5091 -0.6394 +vn -0.8011 -0.0961 -0.5908 +vn -0.6573 -0.6591 -0.3655 +vn -0.2391 -0.9681 -0.0754 +vn 0.8852 0.4588 0.0767 +vn 0.9021 0.4085 0.1388 +vn 0.8811 0.4687 0.0640 +vn 0.2904 0.8054 -0.5168 +vn 0.3300 0.8202 -0.4674 +vn 0.2904 0.8054 -0.5167 +vn 0.2821 0.8019 -0.5266 +vn -0.5017 -0.6937 -0.5168 +vn -0.4577 -0.7465 -0.4829 +vn -0.5017 -0.6937 -0.5167 +vn -0.5092 -0.6840 -0.5224 +vn 0.1198 -0.9898 0.0767 +vn 0.2106 -0.9715 0.1084 +vn 0.1038 -0.9921 0.0711 +vn -0.6136 0.3242 0.7200 +vn 0.9103 -0.4003 -0.1056 +vn 0.9717 0.2112 -0.1056 +vn 0.9944 -0.0021 -0.1058 +vn 0.8032 -0.5862 -0.1058 +vn 0.6620 0.7420 -0.1056 +vn 0.8058 0.5827 -0.1058 +vn 0.0995 0.9894 -0.1056 +vn 0.3093 0.9451 -0.1058 +vn -0.5011 0.8589 -0.1056 +vn -0.3052 0.9464 -0.1058 +vn -0.9103 0.4003 -0.1056 +vn -0.8032 0.5862 -0.1058 +vn -0.9717 -0.2112 -0.1056 +vn -0.9944 0.0021 -0.1058 +vn -0.6620 -0.7420 -0.1056 +vn -0.8057 -0.5828 -0.1058 +vn -0.0995 -0.9894 -0.1056 +vn -0.3093 -0.9451 -0.1058 +vn 0.5011 -0.8589 -0.1056 +vn 0.3052 -0.9464 -0.1058 +vn 0.9026 -0.3958 -0.1693 +vn 0.9628 0.2104 -0.1693 +vn 0.9855 -0.0012 -0.1697 +vn 0.7966 -0.5802 -0.1697 +vn 0.6553 0.7361 -0.1693 +vn 0.7980 0.5783 -0.1697 +vn 0.0975 0.9807 -0.1693 +vn 0.3056 0.9369 -0.1697 +vn -0.4976 0.8507 -0.1693 +vn -0.3034 0.9376 -0.1697 +vn -0.9026 0.3958 -0.1693 +vn -0.7966 0.5802 -0.1697 +vn -0.9628 -0.2104 -0.1693 +vn -0.9855 0.0012 -0.1697 +vn -0.6553 -0.7361 -0.1693 +vn -0.7980 -0.5783 -0.1697 +vn -0.0975 -0.9807 -0.1693 +vn -0.3056 -0.9369 -0.1697 +vn 0.4976 -0.8507 -0.1693 +vn 0.3034 -0.9376 -0.1697 +vn 0.8568 -0.5035 -0.1114 +vn 0.9948 0.0870 -0.0528 +vn 0.9605 0.0008 -0.2783 +vn 0.7838 -0.5686 -0.2498 +vn 0.7578 0.6505 -0.0506 +vn 0.7745 0.5618 -0.2908 +vn 0.2406 0.9656 -0.0985 +vn 0.2989 0.9141 -0.2741 +vn -0.3533 0.9165 -0.1877 +vn -0.2978 0.9213 -0.2501 +vn -0.7986 0.5265 -0.2917 +vn -0.7855 0.5713 -0.2380 +vn -0.9284 -0.0570 -0.3673 +vn -0.9719 0.0003 -0.2353 +vn -0.6939 -0.6125 -0.3785 +vn -0.7871 -0.5710 -0.2332 +vn -0.1862 -0.9294 -0.3187 +vn -0.3017 -0.9254 -0.2295 +vn 0.4040 -0.8894 -0.2141 +vn 0.3003 -0.9252 -0.2319 +vn 0.8934 -0.4462 0.0528 +vn 0.9747 0.1286 0.1829 +vn 0.7165 0.6734 0.1823 +vn 0.2113 0.9762 0.0498 +vn -0.3509 0.9221 -0.1632 +vn -0.7617 0.5253 -0.3793 +vn -0.8533 -0.0754 -0.5160 +vn -0.5739 -0.6382 -0.5132 +vn -0.0446 -0.9269 -0.3725 +vn 0.5109 -0.8452 -0.1567 +vn 0.9172 -0.3575 0.1761 +vn 0.9248 0.2258 0.3061 +vn 0.6181 0.7398 0.2659 +vn 0.1194 0.9902 0.0723 +vn -0.3878 0.8982 -0.2070 +vn -0.7309 0.4835 -0.4817 +vn -0.7574 -0.1325 -0.6394 +vn -0.4248 -0.6859 -0.5908 +vn 0.1049 -0.9249 -0.3655 +vn 0.6073 -0.7909 -0.0754 +vn 0.1939 0.9780 0.0767 +vn 0.2438 0.9598 0.1388 +vn 0.1836 0.9809 0.0640 +vn -0.4481 0.7295 -0.5168 +vn -0.4350 0.7696 -0.4674 +vn -0.4481 0.7295 -0.5167 +vn -0.4506 0.7208 -0.5266 +vn 0.2290 -0.8249 -0.5167 +vn 0.2977 -0.8235 -0.4829 +vn 0.2290 -0.8249 -0.5168 +vn 0.2168 -0.8247 -0.5224 +vn 0.8482 -0.5240 0.0767 +vn 0.8906 -0.4417 0.1084 +vn 0.8400 -0.5379 0.0711 +vn -0.4142 0.9092 0.0435 +vn -0.5528 0.8322 0.0435 +vn -0.2611 0.9644 0.0420 +vn 0.9802 -0.1932 0.0436 +vn 0.9961 0.0736 0.0481 +vn 0.9959 -0.0773 0.0464 +vn 0.9814 -0.1876 0.0406 +vn 0.7488 0.6611 0.0473 +vn 0.8888 0.4559 0.0479 +vn 0.2680 0.9625 0.0421 +vn 0.4205 0.9063 0.0436 +vn -0.0340 0.9986 0.0400 +vn 0.0370 0.9985 0.0400 +vn 0.2483 0.9675 0.0476 +vn -0.0311 0.9985 0.0448 +vn -0.0261 0.9992 -0.0300 +vn 0.2590 0.9653 -0.0322 +vn 0.7491 0.6601 0.0563 +vn 0.7622 0.6464 -0.0358 +vn 0.9961 0.0663 0.0581 +vn 0.9980 0.0519 -0.0350 +vn 0.9874 -0.1491 0.0540 +vn 0.9844 -0.1696 -0.0470 +vn -0.3836 0.9222 0.0495 +vn -0.5416 0.8391 0.0508 +vn -0.8597 0.5084 -0.0494 +vn -0.3728 0.9273 -0.0332 +vn 0.0250 0.9983 -0.0526 +vn 0.3700 0.9272 -0.0580 +vn 0.8676 0.4926 -0.0677 +vn 0.9966 -0.0496 -0.0655 +vn 0.9761 -0.2094 -0.0586 +vn -0.9941 -0.0862 -0.0654 +vn -0.9886 -0.1384 -0.0592 +vn -0.9826 -0.1746 -0.0632 +vn -0.9959 0.0600 -0.0683 +vn -0.7636 0.6423 -0.0664 +vn -0.2611 0.9637 -0.0562 +vn -0.4142 -0.9092 0.0433 +vn -0.2611 -0.9644 0.0420 +vn -0.5528 -0.8322 0.0435 +vn 0.9802 0.1932 0.0436 +vn 0.9814 0.1876 0.0406 +vn 0.9960 0.0773 0.0452 +vn 0.9962 -0.0736 0.0476 +vn 0.8888 -0.4559 0.0479 +vn 0.7488 -0.6611 0.0473 +vn 0.4205 -0.9063 0.0434 +vn 0.2680 -0.9625 0.0421 +vn 0.0370 -0.9985 0.0399 +vn -0.0340 -0.9986 0.0399 +vn 0.2483 -0.9675 0.0476 +vn 0.2590 -0.9653 -0.0322 +vn -0.0261 -0.9992 -0.0300 +vn -0.0311 -0.9985 0.0447 +vn 0.7491 -0.6601 0.0563 +vn 0.7622 -0.6464 -0.0358 +vn 0.9961 -0.0663 0.0581 +vn 0.9980 -0.0519 -0.0350 +vn 0.9874 0.1491 0.0540 +vn 0.9844 0.1696 -0.0470 +vn -0.9970 0.0538 0.0553 +vn -0.9980 0.0505 -0.0380 +vn -0.9896 0.1430 -0.0133 +vn -0.9859 0.1597 0.0502 +vn -0.8783 -0.4746 0.0577 +vn -0.8707 -0.4900 -0.0409 +vn -0.3836 -0.9222 0.0497 +vn -0.3728 -0.9273 -0.0332 +vn -0.0012 0.0003 1.0000 +vn -0.0029 0.0014 1.0000 +vn -0.0021 0.0009 1.0000 +vn -0.0015 0.0002 1.0000 +vn -0.0002 0.0004 1.0000 +vn 0.3700 -0.9272 -0.0581 +vn 0.0250 -0.9983 -0.0525 +vn 0.8676 -0.4926 -0.0677 +vn 0.9966 0.0496 -0.0653 +vn 0.9761 0.2094 -0.0586 +vn -0.9959 -0.0600 -0.0683 +vn -0.9826 0.1746 -0.0632 +vn -0.7636 -0.6423 -0.0664 +vn -0.2611 -0.9637 -0.0562 +vn 0.0000 1.0000 -0.0030 +vn -0.0000 1.0000 -0.0025 +vn -0.0000 1.0000 -0.0007 +vn -0.0000 1.0000 -0.0006 +vn 0.0000 1.0000 -0.0023 +vn -0.0004 -0.0001 1.0000 +vn -0.0014 0.0003 1.0000 +vn -0.0004 0.0002 1.0000 +vn -0.8840 0.4650 0.0479 +vn -0.7414 0.6694 0.0472 +vn -0.2612 0.9644 0.0420 +vn -0.9964 -0.0715 0.0464 +vn -0.9957 0.0790 0.0476 +vn -0.9792 -0.1988 0.0405 +vn -0.9802 -0.1932 0.0437 +vn 0.9802 -0.1932 0.0437 +vn 0.9873 -0.1491 0.0540 +vn -0.9970 -0.0538 0.0560 +vn -0.9859 -0.1597 0.0502 +vn -0.9896 -0.1430 -0.0133 +vn -0.9980 -0.0505 -0.0380 +vn -0.8783 0.4746 0.0577 +vn -0.8707 0.4901 -0.0409 +vn -0.0012 -0.0003 1.0000 +vn -0.0029 -0.0014 1.0000 +vn -0.0022 -0.0010 1.0000 +vn -0.0016 -0.0010 1.0000 +vn 0.9982 -0.0367 -0.0466 +vn 0.9905 0.0157 -0.1367 +vn 0.9979 0.0299 0.0568 +vn 0.9897 -0.0355 0.1387 +vn 0.0091 0.9989 -0.0461 +vn -0.0008 0.9940 -0.1094 +vn -0.0015 0.9980 0.0639 +vn 0.0090 0.9912 0.1324 +vn -0.0107 0.9968 -0.0795 +vn -0.0107 0.9965 0.0826 +vn 0.8978 0.4282 -0.1027 +vn 0.8975 0.4280 0.1066 +vn 0.9182 -0.0050 0.3960 +vn 0.9235 -0.0321 0.3822 +vn 0.7979 0.3806 0.4675 +vn 0.0024 0.9256 0.3786 +vn 0.0084 0.9255 0.3787 +vn -0.0099 0.9255 0.3785 +vn 0.9210 -0.0329 -0.3883 +vn 0.9055 0.0668 -0.4190 +vn -0.0041 0.9255 -0.3786 +vn -0.0099 0.9255 -0.3785 +vn 0.0084 0.9255 -0.3787 +vn 0.7979 0.3806 -0.4675 +vn 0.9979 -0.0444 -0.0466 +vn 0.9906 0.0187 -0.1356 +vn 0.9977 0.0353 0.0581 +vn 0.9895 -0.0432 0.1379 +vn -0.9983 -0.0328 0.0475 +vn -0.9899 0.0390 0.1364 +vn -0.9965 0.0576 -0.0599 +vn -0.9897 -0.0319 -0.1399 +vn -0.9015 0.4209 0.1009 +vn -0.9011 0.4208 -0.1048 +vn 0.9179 -0.0061 0.3968 +vn 0.9232 -0.0393 0.3823 +vn -0.9195 -0.0295 0.3920 +vn -0.8997 0.1006 0.4248 +vn -0.8039 0.3754 0.4612 +vn 0.9211 -0.0401 -0.3873 +vn 0.9035 0.0779 -0.4215 +vn -0.9156 0.0096 -0.4019 +vn -0.9211 -0.0289 -0.3883 +vn -0.8039 0.3754 -0.4612 +vn 0.0055 -1.0000 -0.0000 +vn 0.0046 -1.0000 0.0000 +vn -0.0049 -1.0000 -0.0000 +vn -0.0058 -1.0000 -0.0000 +vn 0.0598 -0.9982 -0.0000 +vn -0.0609 -0.9981 0.0000 +vn 0.9982 0.0367 -0.0466 +vn 0.9897 0.0360 0.1387 +vn 0.9979 -0.0299 0.0568 +vn 0.9905 -0.0157 -0.1367 +vn 0.0091 -0.9989 -0.0461 +vn 0.0090 -0.9912 0.1324 +vn -0.0015 -0.9980 0.0639 +vn -0.0001 -0.9940 -0.1094 +vn -0.0107 -0.9965 0.0826 +vn -0.0107 -0.9968 -0.0795 +vn 0.8975 -0.4280 0.1066 +vn 0.8978 -0.4282 -0.1027 +vn 0.9235 0.0321 0.3822 +vn 0.9182 0.0050 0.3960 +vn 0.7979 -0.3806 0.4675 +vn 0.0084 -0.9255 0.3787 +vn 0.0024 -0.9255 0.3786 +vn -0.0099 -0.9255 0.3785 +vn 0.9055 -0.0668 -0.4190 +vn 0.9210 0.0329 -0.3883 +vn -0.0099 -0.9255 -0.3785 +vn -0.0041 -0.9256 -0.3786 +vn 0.0084 -0.9255 -0.3787 +vn 0.7979 -0.3806 -0.4675 +vn 0.0046 1.0000 0.0000 +vn 0.0055 1.0000 -0.0000 +vn -0.0049 1.0000 0.0000 +vn -0.0058 1.0000 0.0000 +vn 0.0598 0.9982 -0.0000 +vn -0.0609 0.9981 0.0000 +vn -0.1658 0.0000 -0.9862 +vn -0.0243 -0.0000 -0.9997 +vn -0.0962 0.0000 -0.9954 +vn 0.0489 0.0000 -0.9988 +vn -0.0056 0.9993 -0.0376 +vn -0.0066 0.9992 -0.0382 +vn -0.0071 0.9989 -0.0472 +vn -0.0098 0.9984 -0.0556 +vn -0.0166 0.9975 -0.0694 +vn 0.0052 0.9947 -0.1029 +vn 0.0137 0.9931 -0.1165 +vn 0.0698 0.9807 -0.1828 +vn -0.0058 -0.9993 -0.0377 +vn -0.0079 -0.9987 -0.0512 +vn -0.0068 -0.9990 -0.0449 +vn -0.0066 -0.9993 -0.0381 +vn -0.0083 -0.9960 -0.0893 +vn -0.0163 -0.9976 -0.0666 +vn 0.0230 -0.9904 -0.1365 +vn -0.0073 -0.9949 -0.1004 +vn 0.9975 0.0000 -0.0710 +vn 0.8720 0.0000 -0.4894 +vn 0.9567 0.0000 -0.2909 +vn -0.9809 -0.0000 0.1944 +vn -0.9997 0.0000 0.0260 +vn -0.9144 -0.0000 0.4047 +vn 0.4643 -0.0000 -0.8857 +vn 0.6152 -0.0000 -0.7883 +vn -0.6299 0.0000 0.7767 +vn -0.4780 -0.0000 0.8784 +vn 0.3076 0.0000 -0.9515 +vn -0.3466 0.0000 0.9380 +vn 0.0000 0.0483 0.9988 +vn -0.0000 -0.0158 0.9999 +vn 0.0000 0.0161 0.9999 +vn 0.0000 0.0751 -0.9972 +vn 0.0000 0.0001 -1.0000 +vn 0.0000 -0.0472 0.9989 +vn -0.0000 -0.0735 -0.9973 +vn -0.0057 0.9993 -0.0377 +vn -0.0167 0.9975 -0.0694 +vn -0.0066 -0.9993 -0.0382 +vn -0.0164 -0.9976 -0.0667 +vn -0.1374 0.0000 -0.9905 +vn -0.0200 0.0000 -0.9998 +vn -0.0795 0.0000 -0.9968 +vn 0.0404 0.0000 -0.9992 +vn -0.0065 0.9988 -0.0492 +vn -0.0071 0.9987 -0.0496 +vn -0.0077 0.9981 -0.0615 +vn -0.0106 0.9973 -0.0724 +vn -0.0178 0.9958 -0.0902 +vn 0.0056 0.9910 -0.1334 +vn 0.0147 0.9884 -0.1509 +vn 0.0740 0.9692 -0.2350 +vn -0.0065 -0.9988 -0.0492 +vn -0.0084 -0.9977 -0.0665 +vn -0.0073 -0.9983 -0.0582 +vn -0.0071 -0.9987 -0.0496 +vn -0.0089 -0.9932 -0.1159 +vn -0.0174 -0.9961 -0.0864 +vn 0.0246 -0.9840 -0.1765 +vn -0.0079 -0.9915 -0.1302 +vn 0.9963 0.0000 -0.0859 +vn 0.8268 0.0000 -0.5625 +vn 0.9383 -0.0000 -0.3458 +vn -0.9723 0.0000 0.2336 +vn -0.9995 0.0000 0.0315 +vn -0.8812 0.0000 0.4727 +vn 0.3970 -0.0000 -0.9178 +vn 0.5414 0.0000 -0.8408 +vn -0.5561 0.0000 0.8311 +vn -0.4096 0.0000 0.9123 +vn 0.2577 0.0000 -0.9662 +vn -0.2916 0.0000 0.9565 +vn 0.0000 0.0421 0.9991 +vn 0.0000 -0.0137 0.9999 +vn 0.0000 0.0140 0.9999 +vn -0.0000 0.0655 -0.9979 +vn -0.0000 0.0002 -1.0000 +vn 0.0000 -0.0411 0.9992 +vn -0.0000 -0.0641 -0.9979 +vn -0.0201 0.0000 -0.9998 +vn -0.0076 0.9981 -0.0614 +vn -0.0105 0.9973 -0.0723 +vn -0.0180 0.9958 -0.0900 +vn -0.0084 -0.9978 -0.0665 +vn -0.0073 -0.9983 -0.0584 +vn -0.0174 -0.9961 -0.0865 +vn 0.0246 -0.9840 -0.1764 +vn -0.0078 -0.9915 -0.1302 +vn -0.5272 0.8411 0.1206 +vn -0.9353 0.3315 0.1235 +vn -0.8722 0.4889 0.0150 +vn -0.3926 0.9195 0.0191 +vn -0.9522 -0.2793 0.1236 +vn -0.9930 -0.1173 0.0140 +vn -0.6374 -0.7601 0.1261 +vn -0.7607 -0.6487 0.0231 +vn -0.0936 -0.9869 0.1317 +vn -0.2678 -0.9628 0.0357 +vn 0.5262 -0.8397 0.1343 +vn 0.3675 -0.9293 0.0370 +vn 0.9349 -0.3313 0.1276 +vn 0.8750 -0.4836 0.0234 +vn 0.3675 -0.9293 0.0369 +vn 0.9530 0.2795 0.1170 +vn 0.9885 0.1509 0.0116 +vn 0.6385 0.7614 0.1117 +vn 0.7300 0.6834 0.0116 +vn 0.0938 0.9890 0.1144 +vn 0.2242 0.9744 0.0173 +vn 0.6744 0.6898 -0.2633 +vn 0.9169 0.2000 -0.3454 +vn 0.8161 -0.4065 -0.4107 +vn 0.3225 -0.8487 -0.4191 +vn -0.3058 -0.8719 -0.3824 +vn -0.7781 -0.5370 -0.3259 +vn -0.9652 0.0005 -0.2616 +vn -0.7999 0.5636 -0.2062 +vn -0.3362 0.9240 -0.1821 +vn 0.2178 0.9547 -0.2026 +vn -0.1793 -0.9764 0.1206 +vn 0.4659 -0.8762 0.1235 +vn 0.3130 -0.9496 0.0150 +vn -0.3315 -0.9433 0.0191 +vn 0.8909 -0.4371 0.1236 +vn 0.8116 -0.5841 0.0140 +vn 0.9835 0.1301 0.1261 +vn 0.9991 -0.0353 0.0231 +vn 0.7356 0.6645 0.1317 +vn 0.8477 0.5292 0.0357 +vn 0.1790 0.9746 0.1343 +vn 0.3566 0.9335 0.0370 +vn -0.4657 0.8757 0.1276 +vn -0.3187 0.9476 0.0234 +vn 0.3566 0.9335 0.0369 +vn -0.8916 0.4375 0.1170 +vn -0.8309 0.5564 0.0116 +vn -0.9852 -0.1303 0.1117 +vn -0.9999 -0.0110 0.0116 +vn -0.7371 -0.6660 0.1144 +vn -0.8234 -0.5671 0.0173 +vn -0.9632 -0.0533 -0.2633 +vn -0.8113 0.4717 -0.3454 +vn -0.3273 0.8510 -0.4107 +vn 0.3354 0.8437 -0.4191 +vn 0.8144 0.4365 -0.3824 +vn 0.9365 -0.1295 -0.3259 +vn 0.7114 -0.6523 -0.2616 +vn 0.2092 -0.9559 -0.2062 +vn -0.3761 -0.9085 -0.1821 +vn -0.8054 -0.5570 -0.2026 +vn -0.0000 0.9935 -0.1139 +vn -0.8584 0.2261 -0.4605 +vn -0.8540 -0.3275 -0.4042 +vn -0.9397 -0.2288 -0.2542 +vn -0.8789 0.3591 -0.3140 +vn -0.5235 -0.7728 -0.3589 +vn -0.6416 -0.7399 -0.2022 +vn 0.0070 -0.9397 -0.3419 +vn -0.0984 -0.9791 -0.1778 +vn 0.5349 -0.7645 -0.3597 +vn 0.4824 -0.8550 -0.1905 +vn 0.8584 -0.3142 -0.4055 +vn 0.8789 -0.4150 -0.2352 +vn 0.8540 0.2394 -0.4618 +vn 0.9397 0.1729 -0.2950 +vn 0.5235 0.6847 -0.5072 +vn 0.6416 0.6841 -0.3470 +vn -0.0070 0.8516 -0.5241 +vn 0.0984 0.9233 -0.3714 +vn -0.5349 0.6764 -0.5063 +vn -0.4824 0.7991 -0.3587 +vn -0.0140 0.1062 0.9942 +vn -0.0143 0.0969 0.9952 +vn -0.0149 0.1001 0.9949 +vn -0.0127 0.1091 0.9939 +vn -0.0091 0.0895 0.9959 +vn -0.0115 0.0916 0.9957 +vn -0.0005 0.0864 0.9963 +vn -0.0037 0.0868 0.9962 +vn 0.0084 0.0889 0.9960 +vn 0.0056 0.0874 0.9962 +vn 0.0140 0.0962 0.9953 +vn 0.0127 0.0933 0.9956 +vn 0.0143 0.1054 0.9943 +vn 0.0149 0.1022 0.9946 +vn 0.0092 0.1129 0.9936 +vn 0.0115 0.1107 0.9938 +vn 0.0006 0.1160 0.9932 +vn 0.0036 0.1157 0.9933 +vn -0.0084 0.1135 0.9935 +vn -0.0056 0.1150 0.9933 +vn -0.0141 0.1062 0.9942 +vn 0.9495 -0.3123 0.0314 +vn 0.9530 0.3017 -0.0290 +vn 0.9956 0.0928 -0.0089 +vn 0.8203 -0.5424 0.1814 +vn 0.5954 0.7998 -0.0759 +vn 0.7514 0.6568 -0.0629 +vn 0.0135 0.9955 -0.0941 +vn 0.2234 0.9704 -0.0919 +vn -0.1928 0.9769 -0.0926 +vn -0.1927 0.9769 -0.0924 +vn -0.7885 0.6085 0.0898 +vn -0.8944 0.3433 0.2866 +vn -0.8654 0.4767 0.1544 +vn -0.8499 0.5246 -0.0497 +vn -0.8982 -0.2341 0.3721 +vn -0.9418 -0.0447 0.3331 +vn -0.5616 -0.6980 0.4443 +vn -0.7099 -0.5695 0.4144 +vn -0.0196 -0.8818 0.4713 +vn -0.2192 -0.8578 0.4649 +vn 0.5068 -0.7957 0.3317 +vn 0.2986 -0.8359 0.4606 +vn 0.9495 -0.3123 0.0310 +vn 0.8202 -0.5423 0.1820 +vn 0.6824 0.7299 -0.0403 +vn 0.1228 0.9902 -0.0668 +vn 0.2285 0.9701 0.0817 +vn 0.7473 0.6547 0.1138 +vn 0.9813 0.1922 0.0144 +vn 0.9806 0.0962 0.1706 +vn 0.9054 -0.4177 0.0765 +vn 0.8394 -0.4922 0.2305 +vn 0.4836 -0.8667 0.1222 +vn 0.3776 -0.8856 0.2705 +vn -0.1228 -0.9833 0.1340 +vn -0.2285 -0.9338 0.2754 +vn -0.6824 -0.7231 0.1075 +vn -0.7473 -0.6184 0.2433 +vn -0.9813 -0.1853 0.0528 +vn -0.9806 -0.0598 0.1865 +vn -0.9054 0.4245 -0.0092 +vn -0.8394 0.5285 0.1266 +vn -0.4837 0.8735 -0.0549 +vn -0.9054 0.4246 -0.0092 +vn -0.8395 0.5285 0.1266 +vn -0.3776 0.9219 0.0866 +vn -0.8817 -0.4718 0.0000 +vn -0.8810 -0.4715 0.0388 +vn -0.8746 -0.4680 0.1265 +vn 0.8810 0.4715 0.0388 +vn 0.8746 0.4680 0.1265 +vn 0.8817 0.4718 -0.0000 +vn 0.3398 -0.9405 0.0000 +vn 0.4718 -0.8817 0.0000 +vn 0.4714 -0.8808 0.0443 +vn 0.3363 -0.9308 0.1431 +vn -0.5134 0.8581 0.0000 +vn -0.5939 0.8045 -0.0000 +vn -0.5934 0.8037 0.0440 +vn -0.4367 0.8960 0.0803 +vn -0.7242 -0.3875 0.5704 +vn 0.3696 -0.6906 0.6216 +vn 0.2670 -0.7389 0.6186 +vn 0.7242 0.3875 0.5704 +vn -0.4667 0.6321 0.6186 +vn -0.3345 0.7090 0.6208 +vn -0.3417 0.9290 0.1425 +vn -0.2716 0.7386 0.6170 +vn -0.3452 0.9385 0.0000 +vn 0.5893 -0.8079 -0.0000 +vn 0.5833 -0.7996 0.1425 +vn 0.4638 -0.6358 0.6170 +vn -0.0420 -0.0006 0.9991 +vn -0.0150 -0.0002 0.9999 +vn 0.0162 0.0002 0.9999 +vn 0.0387 0.0006 0.9992 +vn 0.0218 0.0003 0.9998 +vn -0.0003 -0.0373 0.9993 +vn -0.0007 -0.0801 0.9968 +vn -0.0004 -0.0482 0.9988 +vn 0.0005 0.0540 0.9985 +vn 0.0015 0.1818 0.9833 +vn 0.9794 -0.2020 -0.0000 +vn 0.9787 -0.2018 0.0388 +vn 0.9715 -0.2003 0.1265 +vn -0.9787 0.2018 0.0388 +vn -0.9715 0.2003 0.1265 +vn -0.9794 0.2020 0.0000 +vn 0.3410 0.9401 -0.0000 +vn 0.2020 0.9794 -0.0000 +vn 0.2017 0.9784 0.0443 +vn 0.3375 0.9304 0.1431 +vn -0.1549 -0.9879 0.0000 +vn -0.0587 -0.9983 0.0000 +vn -0.0587 -0.9973 0.0440 +vn -0.2381 -0.9679 0.0803 +vn 0.8044 -0.1659 0.5704 +vn 0.1582 0.7672 0.6216 +vn 0.2679 0.7386 0.6186 +vn -0.8044 0.1659 0.5704 +vn -0.0461 -0.7843 0.6186 +vn -0.1969 -0.7588 0.6208 +vn -0.3322 -0.9324 0.1425 +vn -0.2641 -0.7413 0.6170 +vn -0.3356 -0.9420 0.0000 +vn 0.0644 0.9979 -0.0000 +vn 0.0637 0.9877 0.1425 +vn 0.0507 0.7853 0.6170 +vn 0.0549 0.0000 0.9985 +vn 0.0196 0.0000 0.9998 +vn -0.0211 0.0000 0.9998 +vn -0.0507 0.0000 0.9987 +vn -0.0284 0.0000 0.9996 +vn 0.5570 0.8305 0.0000 +vn 0.5566 0.8299 0.0388 +vn 0.5525 0.8239 0.1265 +vn -0.5566 -0.8299 0.0388 +vn -0.5525 -0.8239 0.1265 +vn -0.5570 -0.8305 -0.0000 +vn -0.7416 0.6708 0.0000 +vn -0.8305 0.5570 0.0000 +vn -0.8297 0.5564 0.0443 +vn -0.7340 0.6639 0.1431 +vn 0.8562 -0.5166 -0.0000 +vn 0.9021 -0.4315 0.0000 +vn 0.9012 -0.4311 0.0440 +vn 0.8062 -0.5861 0.0803 +vn 0.4575 0.6821 0.5704 +vn 0.4575 0.6822 0.5704 +vn -0.6506 0.4363 0.6216 +vn -0.5827 0.5271 0.6186 +vn -0.4575 -0.6822 0.5704 +vn 0.7088 -0.3390 0.6186 +vn 0.6282 -0.4690 0.6208 +vn 0.7378 -0.6598 0.1425 +vn 0.5866 -0.5246 0.6170 +vn 0.7454 -0.6666 0.0000 +vn -0.8996 0.4366 0.0000 +vn -0.8905 0.4322 0.1425 +vn -0.7080 0.3436 0.6170 +vn 0.0012 0.0716 0.9974 +vn 0.0004 0.0256 0.9997 +vn -0.0005 -0.0276 0.9996 +vn -0.0012 -0.0661 0.9978 +vn -0.0006 -0.0372 0.9993 +vn -0.7138 0.7003 0.0000 +vn -0.7133 0.6998 0.0388 +vn -0.7081 0.6947 0.1265 +vn 0.7133 -0.6998 0.0388 +vn 0.7081 -0.6947 0.1265 +vn 0.7138 -0.7003 0.0000 +vn -0.7959 -0.6055 0.0000 +vn -0.7003 -0.7139 -0.0000 +vn -0.6996 -0.7131 0.0443 +vn -0.7877 -0.5992 0.1431 +vn 0.6654 0.7465 0.0000 +vn 0.5901 0.8073 0.0000 +vn 0.5896 0.8065 0.0440 +vn 0.7245 0.6846 0.0803 +vn -0.5863 0.5752 0.5704 +vn -0.5486 -0.5592 0.6216 +vn -0.6253 -0.4757 0.6186 +vn 0.5863 -0.5752 0.5704 +vn 0.4637 0.6343 0.6186 +vn 0.5766 0.5312 0.6208 +vn 0.7843 0.6038 0.1425 +vn 0.6236 0.4800 0.6170 +vn 0.7924 0.6100 0.0000 +vn -0.5947 -0.8039 0.0000 +vn -0.5887 -0.7957 0.1425 +vn -0.4680 -0.6327 0.6170 +vn 0.1600 -0.1001 0.9820 +vn 0.1607 0.0335 0.9864 +vn -0.0540 -0.1012 0.9934 +vn 0.1600 0.1001 0.9820 +vn 0.0540 0.1012 0.9934 +vn -0.1607 -0.0335 0.9864 +vn -0.1600 -0.1001 0.9820 +vn -0.1600 0.1001 0.9820 +vn -0.2391 0.9236 0.2996 +vn -0.6532 0.6958 0.2986 +vn -0.6061 0.7468 0.2735 +vn -0.1749 0.9445 0.2779 +vn -0.8956 0.3302 0.2981 +vn -0.8849 0.3751 0.2763 +vn -0.8956 0.3301 0.2981 +vn -0.9317 -0.1774 0.3170 +vn -0.9534 -0.0954 0.2861 +vn -0.8849 0.3751 0.2762 +vn -0.6722 -0.6681 0.3189 +vn -0.7319 -0.6221 0.2780 +vn -0.1687 -0.9362 0.3084 +vn -0.2636 -0.9282 0.2627 +vn 0.1585 -0.9463 0.2818 +vn 0.1503 -0.9503 0.2728 +vn 0.5971 -0.7399 0.3097 +vn 0.5026 -0.8068 0.3106 +vn 0.9323 -0.1868 0.3098 +vn 0.9087 -0.2929 0.2975 +vn 0.8915 0.3521 0.2851 +vn 0.9206 0.2798 0.2724 +vn 0.7632 0.5888 0.2661 +vn 0.7716 0.5692 0.2839 +vn 0.3168 0.8994 0.3014 +vn 0.4231 0.8566 0.2953 +vn 0.9455 0.2873 0.1532 +vn 0.9808 0.1877 0.0531 +vn 0.8197 -0.5194 0.2414 +vn 0.8839 -0.4553 0.1072 +vn 0.2853 -0.9228 0.2588 +vn 0.2112 -0.9694 0.1251 +vn 0.2079 -0.9781 0.0023 +vn -0.4871 -0.8562 0.1720 +vn -0.7601 -0.6307 0.1564 +vn -0.8378 -0.5397 0.0824 +vn -0.9642 0.1413 0.2244 +vn -0.9106 0.3753 0.1732 +vn -0.9106 0.3752 0.1732 +vn -0.6147 0.7690 0.1755 +vn -0.1717 0.9679 0.1833 +vn -0.0558 0.9921 0.1121 +vn 0.6770 0.6741 0.2955 +vn 0.7591 0.6207 0.1961 +vn -0.5849 -0.8088 0.0618 +vn -0.5590 -0.8289 0.0198 +vn -0.6404 -0.7507 0.1622 +vn 0.5849 0.8088 -0.0617 +vn 0.5590 0.8289 -0.0198 +vn 0.6404 0.7507 -0.1622 +vn -0.5434 0.8390 -0.0274 +vn -0.5728 0.8171 -0.0660 +vn -0.4663 0.8822 0.0660 +vn 0.5434 -0.8390 0.0274 +vn 0.5728 -0.8171 0.0660 +vn 0.4663 -0.8822 -0.0660 +vn -0.8450 -0.4707 -0.2536 +vn -0.8468 -0.4677 -0.2532 +vn -0.9043 -0.3988 -0.1525 +vn -0.9250 -0.3650 -0.1056 +vn -0.5488 0.2589 -0.7948 +vn -0.5485 0.2613 -0.7943 +vn -0.6071 0.3517 -0.7125 +vn -0.6311 0.3906 -0.6702 +vn 0.3845 0.5828 -0.7159 +vn 0.3835 0.5816 -0.7174 +vn 0.3587 0.6848 -0.6344 +vn 0.3457 0.7285 -0.5915 +vn 0.9767 0.1767 -0.1220 +vn 0.9762 0.1765 -0.1260 +vn 0.9645 0.2632 -0.0202 +vn 0.9531 0.3013 0.0286 +vn 0.7068 -0.5709 0.4178 +vn 0.7076 -0.5709 0.4165 +vn 0.6801 -0.4952 0.5406 +vn 0.6634 -0.4568 0.5927 +vn -0.2511 -0.9039 0.3464 +vn -0.2528 -0.9031 0.3471 +vn -0.2979 -0.8333 0.4657 +vn -0.3171 -0.7959 0.5158 +vn -0.3223 0.7986 0.5084 +vn -0.2970 0.7954 0.5284 +vn -0.3042 0.7986 0.5193 +vn -0.3345 0.7941 0.5074 +vn 0.3067 -0.7997 -0.5162 +vn 0.3386 -0.7926 -0.5071 +vn 0.3263 -0.7971 -0.5081 +vn 0.2994 -0.7964 -0.5254 +vn -0.3522 0.7829 0.5129 +vn -0.3573 0.7751 0.5210 +vn 0.3590 -0.7725 -0.5238 +vn 0.3539 -0.7803 -0.5156 +vn -0.3571 0.7639 0.5376 +vn -0.3499 0.7606 0.5468 +vn 0.3475 -0.7595 -0.5499 +vn 0.3547 -0.7628 -0.5406 +vn -0.3319 0.7607 0.5579 +vn -0.3196 0.7652 0.5589 +vn 0.3154 -0.7667 -0.5592 +vn 0.3278 -0.7622 -0.5583 +vn -0.3016 0.7765 0.5533 +vn -0.2967 0.7842 0.5450 +vn 0.2950 -0.7867 -0.5422 +vn 0.3000 -0.7790 -0.5506 +vn 0.9465 0.3185 0.0526 +vn 0.9463 0.3191 0.0518 +vn 0.9467 0.3178 0.0530 +vn 0.9468 0.3173 0.0543 +vn 0.3576 0.7119 -0.6043 +vn 0.3468 0.7302 -0.5886 +vn 0.3753 0.6742 -0.6361 +vn 0.3910 0.6503 -0.6514 +vn -0.6278 0.3707 -0.6844 +vn -0.6337 0.3916 -0.6671 +vn -0.6130 0.3327 -0.7166 +vn -0.6071 0.3035 -0.7344 +vn -0.9338 -0.3484 -0.0818 +vn -0.9341 -0.3473 -0.0824 +vn -0.9336 -0.3489 -0.0811 +vn -0.9334 -0.3499 -0.0799 +vn -0.3454 -0.7398 0.5775 +vn -0.3348 -0.7582 0.5596 +vn -0.3635 -0.7028 0.6115 +vn -0.3796 -0.6771 0.6305 +vn 0.6404 -0.3992 0.6561 +vn 0.6464 -0.4187 0.6378 +vn 0.6255 -0.3602 0.6921 +vn 0.6193 -0.3322 0.7114 +vn 0.9468 0.3172 0.0547 +vn 0.9468 0.3169 0.0558 +vn 0.4053 0.6178 -0.6738 +vn 0.4101 0.6192 -0.6696 +vn -0.5945 0.2712 -0.7570 +vn -0.5987 0.2659 -0.7556 +vn -0.9335 -0.3498 -0.0793 +vn -0.9332 -0.3506 -0.0787 +vn -0.3946 -0.6446 0.6548 +vn -0.3999 -0.6432 0.6530 +vn 0.6063 -0.2979 0.7374 +vn 0.6109 -0.2961 0.7342 +vn 0.9472 0.3042 0.1014 +vn 0.9520 0.2720 0.1402 +vn 0.9528 0.2703 0.1380 +vn 0.9493 0.2950 0.1092 +vn 0.7777 0.5103 -0.3671 +vn 0.8945 0.4330 -0.1114 +vn 0.8856 0.4315 -0.1717 +vn 0.7692 0.4844 -0.4168 +vn 0.7547 0.6010 -0.2633 +vn 0.8483 0.5186 -0.1068 +vn 0.9458 0.3108 0.0946 +vn 0.9521 0.2720 0.1401 +vn 0.7022 0.6751 -0.2264 +vn 0.8892 0.4562 -0.0355 +vn 0.9497 0.2894 0.1200 +vn 0.9520 0.2721 0.1402 +vn -0.9316 -0.3621 -0.0327 +vn -0.9283 -0.3709 -0.0248 +vn -0.9193 -0.3935 0.0042 +vn -0.9200 -0.3918 0.0064 +vn -0.8681 -0.0696 -0.4915 +vn -0.8377 -0.0817 -0.5399 +vn -0.9292 -0.2110 -0.3036 +vn -0.9449 -0.2184 -0.2440 +vn -0.9220 0.0105 -0.3870 +vn -0.9637 -0.1225 -0.2373 +vn -0.9337 -0.3558 -0.0395 +vn -0.9200 -0.3918 0.0063 +vn -0.9329 0.0998 -0.3461 +vn -0.9651 -0.2009 -0.1682 +vn -0.9265 -0.3759 -0.0140 +vn -0.9201 -0.3917 0.0064 +vn 0.3440 -0.8900 -0.2992 +vn 0.3431 -0.9273 -0.1499 +vn 0.3446 -0.9118 -0.2233 +vn -0.2506 0.4823 0.8394 +vn -0.2756 0.5705 0.7736 +vn -0.2244 0.3947 0.8910 +vn -0.5897 0.1851 0.7861 +vn 0.0604 0.3267 0.9432 +vn -0.1817 0.2962 0.9377 +vn -0.7498 0.1111 0.6522 +vn 0.6726 0.2914 0.6802 +vn 0.4920 0.3225 0.8086 +vn 0.9703 0.1290 0.2047 +vn 0.9057 0.1936 0.3772 +vn 0.9492 -0.0897 -0.3015 +vn 0.9915 -0.0107 -0.1298 +vn 0.5995 -0.3203 -0.7335 +vn 0.7591 -0.2441 -0.6035 +vn -0.0429 -0.4637 -0.8850 +vn 0.1959 -0.4331 -0.8798 +vn -0.6504 -0.4232 -0.6307 +vn -0.4703 -0.4558 -0.7557 +vn -0.9528 -0.2557 -0.1635 +vn -0.8860 -0.3216 -0.3341 +vn -0.9381 -0.0389 0.3442 +vn -0.9784 -0.1165 0.1709 +vn -0.6663 0.3012 0.6821 +vn -0.0947 0.6433 0.7597 +vn -0.2017 0.5118 0.8351 +vn -0.7502 0.1681 0.6395 +vn 0.5086 0.6668 0.5447 +vn 0.4377 0.5981 0.6713 +vn 0.8932 0.4174 0.1673 +vn 0.8671 0.4083 0.2854 +vn 0.9719 -0.0018 -0.2355 +vn 0.9850 0.0512 -0.1646 +vn 0.7030 -0.4718 -0.5321 +vn 0.7504 -0.3563 -0.5567 +vn 0.1556 -0.7953 -0.5860 +vn 0.2079 -0.6551 -0.7264 +vn -0.4188 -0.8245 -0.3806 +vn -0.3950 -0.7092 -0.5839 +vn -0.8059 -0.5913 -0.0295 +vn -0.8102 -0.5405 -0.2267 +vn -0.9142 -0.1818 0.3622 +vn -0.9487 -0.2284 0.2188 +vn -0.9142 -0.1819 0.3622 +vn -0.6601 0.4640 0.5907 +vn -0.1586 0.8347 0.5273 +vn 0.4015 0.8613 0.3112 +vn 0.8240 0.5657 0.0326 +vn 0.9761 0.0344 -0.2144 +vn 0.7674 -0.5492 -0.3308 +vn 0.2735 -0.9250 -0.2639 +vn -0.2882 -0.9560 -0.0543 +vn -0.7227 -0.6554 0.2194 +vn -0.8760 -0.1165 0.4680 +vn -0.6898 0.6156 0.3811 +vn -0.2231 0.9522 0.2085 +vn 0.3243 0.9457 0.0239 +vn 0.7846 0.6090 -0.1160 +vn 0.9876 0.0339 -0.1532 +vn 0.8300 -0.5540 -0.0649 +vn 0.3844 -0.9169 0.1078 +vn -0.1803 -0.9370 0.2992 +vn -0.6729 -0.5921 0.4435 +vn -0.8789 0.0162 0.4767 +vn 0.7089 0.6882 -0.1543 +vn 0.6447 0.7432 -0.1789 +vn 0.7089 0.6884 -0.1534 +vn 0.7213 0.6766 -0.1481 +vn 0.9797 -0.1798 0.0886 +vn 0.9902 -0.1312 0.0484 +vn 0.9771 -0.1896 0.0968 +vn -0.5265 -0.4770 0.7037 +vn -0.5714 -0.4093 0.7113 +vn -0.5183 -0.4886 0.7019 +vn -0.7933 0.4336 0.4274 +vn -0.7512 0.5227 0.4031 +vn -0.7999 0.4175 0.4312 +vn -0.5897 -0.1851 0.7861 +vn -0.7498 -0.1111 0.6522 +vn -0.1816 -0.2953 0.9380 +vn 0.0605 -0.3263 0.9433 +vn 0.4920 -0.3225 0.8086 +vn 0.6726 -0.2914 0.6802 +vn 0.9057 -0.1936 0.3772 +vn 0.9703 -0.1290 0.2047 +vn 0.9915 0.0107 -0.1298 +vn 0.9492 0.0897 -0.3015 +vn 0.7591 0.2441 -0.6035 +vn 0.5995 0.3203 -0.7335 +vn 0.1959 0.4331 -0.8798 +vn -0.0429 0.4637 -0.8850 +vn -0.4701 0.4568 -0.7552 +vn -0.6504 0.4232 -0.6308 +vn -0.8860 0.3215 -0.3341 +vn -0.9528 0.2557 -0.1635 +vn -0.9784 0.1164 0.1709 +vn -0.9381 0.0389 0.3442 +vn -0.9381 0.0388 0.3442 +vn -0.9784 0.1165 0.1708 +vn -0.6663 -0.3012 0.6822 +vn -0.7502 -0.1681 0.6395 +vn -0.2017 -0.5118 0.8351 +vn -0.0947 -0.6433 0.7597 +vn 0.4377 -0.5981 0.6713 +vn 0.5086 -0.6668 0.5447 +vn 0.8671 -0.4083 0.2854 +vn 0.8932 -0.4174 0.1673 +vn 0.9850 -0.0512 -0.1646 +vn 0.9719 0.0018 -0.2355 +vn 0.7504 0.3563 -0.5567 +vn 0.7030 0.4718 -0.5321 +vn 0.2079 0.6551 -0.7264 +vn 0.1556 0.7953 -0.5859 +vn -0.3950 0.7092 -0.5839 +vn -0.4188 0.8245 -0.3806 +vn -0.8102 0.5406 -0.2267 +vn -0.8059 0.5913 -0.0295 +vn -0.9487 0.2284 0.2188 +vn -0.9142 0.1819 0.3622 +vn -0.9142 0.1818 0.3622 +vn -0.1586 -0.8347 0.5273 +vn -0.6601 -0.4640 0.5907 +vn 0.4015 -0.8613 0.3112 +vn 0.8240 -0.5657 0.0326 +vn 0.9761 -0.0344 -0.2144 +vn 0.7674 0.5492 -0.3308 +vn 0.2735 0.9250 -0.2639 +vn -0.2882 0.9560 -0.0543 +vn -0.7227 0.6554 0.2194 +vn -0.8760 0.1165 0.4680 +vn -0.2231 -0.9522 0.2084 +vn -0.6898 -0.6156 0.3811 +vn 0.3243 -0.9457 0.0239 +vn 0.7846 -0.6090 -0.1160 +vn 0.9876 -0.0339 -0.1532 +vn 0.8300 0.5540 -0.0649 +vn 0.3844 0.9169 0.1078 +vn -0.1803 0.9370 0.2992 +vn -0.6729 0.5921 0.4435 +vn -0.8789 -0.0163 0.4768 +vn -0.8789 -0.0162 0.4767 +vn 0.7089 -0.6884 -0.1534 +vn 0.7213 -0.6766 -0.1481 +vn 0.7088 -0.6885 -0.1534 +vn 0.6447 -0.7432 -0.1788 +vn 0.9797 0.1798 0.0886 +vn 0.9771 0.1896 0.0968 +vn 0.9902 0.1312 0.0484 +vn -0.5265 0.4770 0.7037 +vn -0.5183 0.4886 0.7019 +vn -0.5714 0.4093 0.7113 +vn -0.7933 -0.4336 0.4274 +vn -0.7999 -0.4175 0.4312 +vn -0.7512 -0.5227 0.4031 +vn 0.8663 0.4992 -0.0162 +usemtl Texture1 +s off +f 1/1/1 2/2/1 3/3/1 +f 4/4/1 5/5/1 1/1/1 +f 1/1/1 5/5/1 2/2/1 +f 44/6/2 45/7/2 46/8/2 +f 50/9/2 51/10/2 52/11/2 +f 53/12/2 54/13/2 55/14/2 +f 56/15/1 57/16/1 58/17/1 +f 59/18/1 60/19/1 61/20/1 +f 98/21/3 99/22/3 100/23/3 +f 101/24/3 102/25/3 103/26/3 +f 224/27/2 225/28/2 226/29/2 +f 227/30/2 228/31/2 229/32/2 +f 230/33/2 231/34/2 232/35/2 +f 233/36/2 234/37/2 235/38/2 +f 236/39/2 235/38/2 237/40/2 +f 238/41/4 239/42/4 240/43/4 +f 241/44/4 238/41/4 242/45/4 +f 243/46/2 244/47/2 245/48/2 +f 246/49/2 245/48/2 247/50/2 +f 248/51/4 249/52/4 250/53/4 +f 251/54/4 248/51/4 252/55/4 +f 253/56/2 254/57/2 255/58/2 +f 256/59/2 257/60/2 254/57/2 +f 258/61/4 259/62/4 260/63/4 +f 261/64/4 262/65/4 258/61/4 +f 263/66/5 264/67/5 265/68/5 +f 266/69/6 267/70/6 268/71/6 +f 269/72/5 270/73/5 271/74/5 +f 272/75/6 273/76/6 274/77/6 +f 275/78/5 276/79/5 277/80/5 +f 278/81/6 279/82/6 280/83/6 +f 281/84/5 282/85/5 283/86/5 +f 284/87/6 285/88/6 286/89/6 +f 287/90/7 288/91/7 289/92/7 +f 290/93/7 291/94/7 292/95/7 +f 293/96/8 294/97/8 295/98/8 +f 296/99/8 297/100/8 298/101/8 +f 313/102/9 314/103/9 315/104/9 +f 328/105/10 329/106/10 330/107/10 +f 334/108/11 335/109/11 336/110/11 +f 337/111/12 338/112/12 339/113/12 +f 344/114/13 4311/115/13 4310/116/13 +f 344/114/13 4310/116/13 345/117/13 +f 346/118/14 4476/119/14 4475/120/14 +f 346/118/14 4475/120/14 347/121/14 +f 4106/122/15 348/123/15 349/124/15 +f 4106/122/15 349/124/15 4105/125/15 +f 350/126/1 351/127/1 352/128/1 +f 350/126/1 352/128/1 353/129/1 +f 354/130/1 355/131/1 356/132/1 +f 354/130/1 357/133/1 360/134/1 +f 354/130/1 360/134/1 361/135/1 +f 362/136/1 354/130/1 361/135/1 +f 362/136/1 361/135/1 363/137/1 +f 353/129/1 352/128/1 364/138/1 +f 352/128/1 362/136/1 363/137/1 +f 352/128/1 363/137/1 364/138/1 +f 366/139/5 367/140/5 368/141/5 +f 366/139/5 368/141/5 369/142/5 +f 3602/143/5 398/144/5 399/145/5 +f 3602/143/5 399/145/5 3599/146/5 +f 3599/146/5 399/145/5 404/147/5 +f 3599/146/5 404/147/5 3601/148/5 +f 3601/148/5 404/147/5 409/149/5 +f 3601/148/5 409/149/5 3603/150/5 +f 3603/150/5 414/151/5 3600/152/5 +f 4193/153/16 4191/154/16 423/155/16 +f 4193/153/16 423/155/16 424/156/16 +f 4240/157/17 4218/158/17 4217/159/17 +f 4240/157/17 4217/159/17 4239/160/17 +f 429/161/18 431/162/18 432/163/18 +f 429/161/18 432/163/18 433/164/18 +f 429/161/18 433/164/18 434/165/18 +f 4200/166/19 4234/167/19 447/168/19 +f 4199/169/20 458/170/20 4232/171/20 +f 462/172/21 463/173/21 4138/174/21 +f 462/172/21 4138/174/21 4174/175/21 +f 4137/176/22 465/177/22 4127/178/22 +f 4137/176/22 4127/178/22 4139/179/22 +f 466/180/23 467/181/23 468/182/23 +f 470/183/24 4456/184/24 4473/185/24 +f 472/186/25 4448/187/25 4472/188/25 +f 472/186/25 4472/188/25 473/189/25 +f 474/190/26 4459/191/26 4474/192/26 +f 474/190/26 4474/192/26 475/193/26 +f 476/194/27 4465/195/27 4478/196/27 +f 476/194/27 4478/196/27 477/197/27 +f 4462/198/28 478/199/28 479/200/28 +f 4462/198/28 479/200/28 4477/201/28 +f 480/202/29 481/203/29 482/204/29 +f 480/202/29 482/204/29 483/205/29 +f 484/206/29 482/204/29 481/203/29 +f 484/206/29 481/203/29 485/207/29 +f 4238/208/30 558/209/30 559/210/30 +f 4238/208/30 559/210/30 4246/211/30 +f 560/212/31 561/213/31 562/214/31 +f 560/212/31 562/214/31 563/215/31 +f 4238/208/30 564/216/30 558/209/30 +f 563/215/31 562/214/31 565/217/31 +f 563/215/31 565/217/31 566/218/31 +f 4222/219/30 4213/220/30 567/221/30 +f 4222/219/30 567/221/30 564/216/30 +f 568/222/31 570/223/31 571/224/31 +f 4214/225/30 4224/226/30 572/227/30 +f 4214/225/30 572/227/30 573/228/30 +f 571/224/31 570/223/31 574/229/31 +f 571/224/31 574/229/31 575/230/31 +f 4224/226/30 4236/231/30 576/232/30 +f 575/230/31 574/229/31 577/233/31 +f 575/230/31 577/233/31 578/234/31 +f 4236/231/30 4247/235/30 579/236/30 +f 4236/231/30 579/236/30 576/232/30 +f 560/212/31 580/237/31 581/238/31 +f 560/212/31 581/238/31 561/213/31 +f 589/239/32 4460/240/32 4466/241/32 +f 4430/242/33 591/243/33 4449/244/33 +f 4430/242/33 4449/244/33 4461/245/33 +f 4426/246/34 592/247/34 4457/248/34 +f 4426/246/34 4457/248/34 4450/249/34 +f 4429/250/35 593/251/35 4463/252/35 +f 4429/250/35 4463/252/35 4458/253/35 +f 4435/254/36 4469/255/36 4464/256/36 +f 595/257/37 4436/258/37 4467/259/37 +f 627/260/38 628/261/38 629/262/38 +f 627/260/38 629/262/38 624/263/38 +f 638/264/39 639/265/39 640/266/39 +f 644/267/40 645/268/40 646/269/40 +f 644/267/40 646/269/40 647/270/40 +f 654/271/41 655/272/41 656/273/41 +f 654/271/41 656/273/41 657/274/42 +f 660/275/43 662/276/43 663/277/44 +f 682/278/1 683/279/1 684/280/1 +f 4248/281/45 4291/282/45 686/283/45 +f 4248/281/45 686/283/45 687/284/45 +f 688/285/46 689/286/46 4278/287/46 +f 688/285/46 4278/287/46 4244/288/46 +f 692/289/47 4245/290/47 4249/291/47 +f 692/289/47 4249/291/47 693/292/47 +f 694/293/1 695/294/1 696/295/1 +f 694/293/1 696/295/1 697/296/1 +f 4341/297/48 4377/298/48 698/299/48 +f 4341/297/48 698/299/48 699/300/48 +f 700/301/49 701/302/49 4379/303/49 +f 700/301/49 4379/303/49 4339/304/49 +f 4380/305/50 702/306/50 703/307/50 +f 4380/305/50 703/307/50 4378/308/50 +f 704/309/51 4342/310/51 705/311/51 +f 722/312/52 723/313/52 724/314/52 +f 722/312/52 724/314/52 725/315/52 +f 726/316/53 727/317/53 4494/318/53 +f 726/316/53 4494/318/53 4425/319/53 +f 740/320/1 741/321/1 742/322/1 +f 740/320/1 742/322/1 743/323/1 +f 743/323/1 742/322/1 748/324/1 +f 743/323/1 748/324/1 749/325/1 +f 752/326/1 753/327/1 754/328/1 +f 753/327/1 760/329/1 761/330/1 +f 764/331/1 765/332/1 766/333/1 +f 764/331/1 766/333/1 767/334/1 +f 4110/335/54 4471/336/54 768/337/54 +f 4110/335/54 768/337/54 769/338/54 +f 770/339/55 771/340/55 4468/341/55 +f 770/339/55 4468/341/55 4115/342/55 +f 776/343/56 777/344/56 778/345/56 +f 776/343/56 778/345/56 779/346/56 +f 780/347/57 4123/348/57 4087/349/57 +f 780/347/57 4087/349/57 781/350/57 +f 46/8/2 45/7/2 48/351/2 +f 46/8/2 48/351/2 47/352/2 +f 822/353/5 823/354/5 4670/355/5 +f 822/353/5 4670/355/5 4672/356/5 +f 824/357/5 822/353/5 4672/356/5 +f 824/357/5 4672/356/5 4673/358/5 +f 825/359/5 824/357/5 4673/358/5 +f 825/359/5 4673/358/5 4674/360/5 +f 826/361/6 827/362/6 3575/363/6 +f 826/361/6 3575/363/6 3573/364/6 +f 827/362/6 828/365/6 3576/366/6 +f 827/362/6 3576/366/6 3575/363/6 +f 828/365/6 3577/367/6 3576/366/6 +f 44/6/2 50/9/2 52/11/2 +f 49/368/2 48/351/2 54/13/2 +f 49/368/2 54/13/2 53/12/2 +f 830/369/58 3574/370/58 4671/371/58 +f 830/369/58 4671/371/58 831/372/58 +f 3578/373/1 832/374/1 833/375/1 +f 3578/373/1 833/375/1 4675/376/1 +f 54/13/2 52/11/2 51/10/2 +f 54/13/2 51/10/2 55/14/2 +f 834/377/59 835/378/59 836/379/59 +f 834/377/59 836/379/59 837/380/59 +f 4525/381/1 838/382/1 839/383/1 +f 4525/381/1 839/383/1 840/384/1 +f 841/385/60 4523/386/60 4500/387/60 +f 841/385/60 4500/387/60 842/388/60 +f 843/389/61 4515/390/61 4526/391/61 +f 843/389/61 4526/391/61 4524/392/61 +f 844/393/62 845/394/62 846/395/62 +f 844/393/62 846/395/62 847/396/62 +f 4063/397/1 848/398/1 849/399/1 +f 4063/397/1 849/399/1 850/400/1 +f 851/401/63 852/402/63 4083/403/63 +f 851/401/63 4083/403/63 4067/404/63 +f 853/405/64 4062/406/64 4064/407/64 +f 853/405/64 4064/407/64 4071/408/64 +f 854/409/1 855/410/1 856/411/1 +f 854/409/1 856/411/1 857/412/1 +f 56/15/1 58/17/1 855/410/1 +f 56/15/1 858/413/1 859/414/1 +f 56/15/1 859/414/1 57/16/1 +f 57/16/1 860/415/1 861/416/1 +f 862/417/1 863/418/1 864/419/1 +f 862/417/1 864/419/1 865/420/1 +f 59/18/1 862/417/1 865/420/1 +f 59/18/1 865/420/1 60/19/1 +f 59/18/1 61/20/1 866/421/1 +f 61/20/1 60/19/1 868/422/1 +f 61/20/1 868/422/1 869/423/1 +f 857/412/1 856/411/1 870/424/1 +f 864/419/1 863/418/1 871/425/1 +f 864/419/1 871/425/1 870/424/1 +f 872/426/65 873/427/65 874/428/65 +f 872/426/65 874/428/65 875/429/65 +f 880/430/66 4479/431/66 4480/432/66 +f 880/430/66 4480/432/66 881/433/66 +f 886/434/67 887/435/67 888/436/67 +f 886/434/67 888/436/67 889/437/67 +f 4492/438/68 894/439/68 895/440/68 +f 4492/438/68 895/440/68 4493/441/68 +f 4427/442/69 4481/443/69 4482/444/69 +f 4427/442/69 4482/444/69 4428/445/69 +f 4408/446/70 896/447/70 897/448/70 +f 4408/446/70 897/448/70 4409/449/70 +f 904/450/71 905/451/71 906/452/71 +f 904/450/71 906/452/71 907/453/71 +f 4068/454/72 916/455/72 917/456/72 +f 4068/454/72 917/456/72 4069/457/72 +f 4102/458/73 920/459/73 921/460/73 +f 4102/458/73 921/460/73 4103/461/73 +f 4135/462/74 922/463/74 923/464/74 +f 4135/462/74 923/464/74 4136/465/74 +f 924/466/75 925/467/75 926/468/75 +f 924/466/75 926/468/75 927/469/75 +f 925/467/75 929/470/75 926/468/75 +f 930/471/76 931/472/76 4299/473/76 +f 930/471/76 4299/473/76 4273/474/76 +f 4298/475/77 932/476/77 4323/477/77 +f 4298/475/77 4323/477/77 4300/478/77 +f 4324/479/78 933/480/78 934/481/78 +f 4324/479/78 934/481/78 4329/482/78 +f 935/483/79 4274/484/79 4266/485/79 +f 935/483/79 4266/485/79 936/486/79 +f 1051/487/3 1053/488/3 1054/489/3 +f 1054/489/3 1055/490/3 1056/491/3 +f 1054/489/3 1056/491/3 1051/487/3 +f 1052/492/3 102/25/3 101/24/3 +f 1052/492/3 101/24/3 1053/488/3 +f 1055/490/3 100/23/3 99/22/3 +f 1055/490/3 99/22/3 1056/491/3 +f 1134/493/4 1135/494/4 1136/495/4 +f 1134/493/4 1136/495/4 1137/496/4 +f 1179/497/4 1180/498/4 1181/499/4 +f 1179/497/4 1181/499/4 1182/500/4 +f 1230/501/2 1231/502/2 1232/503/2 +f 1230/501/2 1232/503/2 1233/504/2 +f 1230/501/2 1233/504/2 1234/505/2 +f 1230/501/2 1234/505/2 1235/506/2 +f 1270/507/2 1271/508/2 1272/509/2 +f 1270/507/2 1272/509/2 1273/510/2 +f 1270/507/2 1273/510/2 1274/511/2 +f 1270/507/2 1274/511/2 1275/512/2 +f 1342/513/80 4026/514/80 4009/515/80 +f 1342/513/81 4009/515/81 1343/516/81 +f 1489/517/82 1490/518/82 1491/519/82 +f 1489/517/82 1491/519/82 1492/520/82 +f 1490/518/82 1493/521/82 1494/522/82 +f 1490/518/82 1494/522/82 1491/519/82 +f 1492/520/82 1495/523/82 1496/524/82 +f 1497/525/83 1496/524/83 1495/523/83 +f 1494/522/82 1493/521/82 1499/526/82 +f 1494/522/82 1499/526/82 1500/527/82 +f 1501/528/84 1502/529/84 1503/530/84 +f 4280/531/85 1505/532/85 1506/533/85 +f 4280/531/85 1506/533/85 4253/534/85 +f 4265/535/86 1507/536/86 1508/537/86 +f 4265/535/86 1508/537/86 4227/538/86 +f 4294/539/87 1511/540/87 1512/541/87 +f 4294/539/87 1512/541/87 4259/542/87 +f 4309/543/88 1513/544/88 1514/545/88 +f 4309/543/88 1514/545/88 4284/546/88 +f 1515/547/89 1516/548/89 1517/549/89 +f 1515/547/89 1517/549/89 1518/550/89 +f 1515/547/89 1518/550/89 1519/551/89 +f 1515/547/89 1519/551/89 1520/552/89 +f 1521/553/90 1522/554/90 1523/555/90 +f 1521/553/90 1523/555/90 1524/556/90 +f 4595/557/91 1525/558/91 1526/559/91 +f 4602/560/92 4636/561/92 1527/562/92 +f 4602/560/92 1527/562/92 1528/563/92 +f 1529/564/93 1530/565/93 4637/566/93 +f 1529/564/93 4637/566/93 4630/567/93 +f 1531/568/94 1532/569/94 4596/570/94 +f 1531/568/94 4596/570/94 4603/571/94 +f 1533/572/94 1534/573/94 1535/574/94 +f 1533/572/94 1535/574/94 1536/575/94 +f 1537/576/93 1538/577/93 1539/578/93 +f 1537/576/93 1539/578/93 1540/579/93 +f 4648/580/92 4582/581/92 4583/582/92 +f 4648/580/92 4583/582/92 4649/583/92 +f 1543/584/95 1544/585/95 1545/586/95 +f 1543/584/95 1545/586/95 1546/587/95 +f 4662/588/93 4656/589/93 1547/590/93 +f 4662/588/93 1547/590/93 1548/591/93 +f 1549/592/96 1550/593/96 1551/594/96 +f 1549/592/96 1551/594/96 1552/595/96 +f 1553/596/90 1554/597/90 4657/598/90 +f 1553/596/90 4657/598/90 4628/599/90 +f 1557/600/94 1558/601/94 1559/602/94 +f 1557/600/94 1559/602/94 1560/603/94 +f 1561/604/93 1562/605/93 1563/606/93 +f 1561/604/93 1563/606/93 1564/607/93 +f 1565/608/97 4654/609/97 4651/610/97 +f 1565/608/97 4651/610/97 1566/611/97 +f 4660/612/98 1567/613/98 1568/614/98 +f 4660/612/98 1568/614/98 4661/615/98 +f 1569/616/99 1570/617/99 1571/618/99 +f 1569/616/99 1571/618/99 1572/619/99 +f 4576/620/2 4550/621/2 1573/622/2 +f 4576/620/2 1573/622/2 1574/623/2 +f 4658/624/100 4632/625/100 1575/626/100 +f 1577/627/90 1578/628/90 4633/629/90 +f 1577/627/90 4633/629/90 4551/630/90 +f 1579/631/92 1580/632/92 4577/633/92 +f 1579/631/92 4577/633/92 4659/634/92 +f 1581/635/94 1582/636/94 1583/637/94 +f 4580/638/2 4553/639/2 1585/640/2 +f 4580/638/2 1585/640/2 1586/641/2 +f 4652/642/93 4626/643/93 1587/644/93 +f 4652/642/93 1587/644/93 1588/645/93 +f 1589/646/101 1590/647/101 1591/648/101 +f 1589/646/101 1591/648/101 1592/649/101 +f 1595/650/92 1596/651/92 4581/652/92 +f 1595/650/92 4581/652/92 4653/653/92 +f 1633/654/102 1634/655/102 3607/656/102 +f 1633/654/102 3607/656/102 3596/657/102 +f 3742/658/103 3846/659/103 3858/660/103 +f 3858/660/103 3759/661/103 3742/658/103 +f 3788/662/103 3759/661/103 3848/663/103 +f 3758/664/103 3785/665/103 3816/666/103 +f 3758/664/103 3816/666/103 3846/659/103 +f 1725/667/2 1726/668/2 1727/669/2 +f 1725/667/2 1727/669/2 1728/670/2 +f 1726/668/2 1729/671/2 1730/672/2 +f 1726/668/2 1730/672/2 1727/669/2 +f 1729/671/2 1731/673/2 1732/674/2 +f 1729/671/2 1732/674/2 1730/672/2 +f 1731/673/2 1733/675/2 1734/676/2 +f 1731/673/2 1734/676/2 1732/674/2 +f 1733/675/2 1735/677/2 1736/678/2 +f 1733/675/2 1736/678/2 1734/676/2 +f 1735/677/2 1737/679/2 1738/680/2 +f 1735/677/2 1738/680/2 1736/678/2 +f 1739/681/2 1740/682/2 1741/683/2 +f 1739/681/2 1741/683/2 1742/684/2 +f 1740/682/2 1725/667/2 1728/670/2 +f 1740/682/2 1728/670/2 1741/683/2 +f 1760/685/104 1761/686/104 3834/687/104 +f 1760/685/104 3834/687/104 3890/688/104 +f 1772/689/105 1773/690/105 1774/691/105 +f 1772/689/105 1774/691/105 1775/692/105 +f 1776/693/106 1777/694/106 1778/695/106 +f 1776/693/106 1778/695/106 1779/696/106 +f 3900/697/107 1780/698/107 1781/699/107 +f 3900/697/107 1781/699/107 3868/700/107 +f 3887/701/108 3853/702/108 3870/703/108 +f 3887/701/108 3870/703/108 3920/704/108 +f 1782/705/109 1783/706/109 1784/707/109 +f 1782/705/109 1784/707/109 1785/708/109 +f 1786/709/110 1787/710/110 1788/711/110 +f 1786/709/110 1788/711/110 1789/712/110 +f 3885/713/111 3912/714/111 3879/715/111 +f 3885/713/111 3879/715/111 3855/716/111 +f 3897/717/112 3872/718/112 1790/719/112 +f 3897/717/112 1790/719/112 1791/720/112 +f 1792/721/113 1793/722/113 1794/723/113 +f 1792/721/113 1794/723/113 1795/724/113 +f 1796/725/114 1797/726/114 1798/727/114 +f 1796/725/114 1798/727/114 1799/728/114 +f 3695/729/115 1800/730/115 1801/731/115 +f 3695/729/115 1801/731/115 3669/732/115 +f 3685/733/116 3644/734/116 3671/735/116 +f 3685/733/116 3671/735/116 3712/736/116 +f 1802/737/117 1803/738/117 1804/739/117 +f 1802/737/117 1804/739/117 1805/740/117 +f 1806/741/118 1807/742/118 1808/743/118 +f 1806/741/118 1808/743/118 1809/744/118 +f 3682/745/119 3706/746/119 3679/747/119 +f 3682/745/119 3679/747/119 3652/748/119 +f 3692/749/120 3670/750/120 1810/751/120 +f 3692/749/120 1810/751/120 1811/752/120 +f 3810/753/2 3825/754/2 3995/755/2 +f 3810/753/2 3995/755/2 4011/756/2 +f 4011/756/2 3996/757/2 3827/758/2 +f 4011/756/2 3827/758/2 3810/753/2 +f 3867/759/2 3827/758/2 3996/757/2 +f 3867/759/2 3996/757/2 3946/760/2 +f 3825/754/2 3861/761/2 3939/762/2 +f 3825/754/2 3939/762/2 3995/755/2 +f 1832/763/121 1833/764/121 1834/765/121 +f 1832/763/121 1834/765/121 1835/766/121 +f 3979/767/122 1836/768/122 1837/769/122 +f 3979/767/122 1837/769/122 3959/770/122 +f 1838/771/123 1839/772/123 1840/773/123 +f 1838/771/123 1840/773/123 1841/774/123 +f 3998/775/124 3981/776/124 3972/777/124 +f 1842/778/125 1843/779/125 1844/780/125 +f 1842/778/125 1844/780/125 1845/781/125 +f 3843/782/126 1846/783/126 1847/784/126 +f 1848/785/127 1849/786/127 1850/787/127 +f 1848/785/127 1850/787/127 1851/788/127 +f 3903/789/128 3860/790/128 3869/791/128 +f 3903/789/128 3869/791/128 3896/792/128 +f 1852/793/129 1853/794/129 1854/795/129 +f 1852/793/129 1854/795/129 1855/796/129 +f 3963/797/130 1856/798/130 1857/799/130 +f 1858/800/131 1859/801/131 1860/802/131 +f 1858/800/131 1860/802/131 1861/803/131 +f 3891/804/132 3940/805/132 3926/806/132 +f 3891/804/132 3926/806/132 3899/807/132 +f 1862/808/133 1863/809/133 1864/810/133 +f 1862/808/133 1864/810/133 1865/811/133 +f 3831/812/134 1866/813/134 1867/814/134 +f 3831/812/134 1867/814/134 3847/815/134 +f 1868/816/135 1870/817/135 1871/818/135 +f 3821/819/136 3801/820/136 3830/821/136 +f 3821/819/136 3830/821/136 3837/822/136 +f 1872/823/129 1873/824/129 1874/825/129 +f 1872/823/129 1874/825/129 1875/826/129 +f 3722/827/130 1876/828/130 1877/829/130 +f 3722/827/130 1877/829/130 3711/830/130 +f 1878/831/131 1879/832/131 1880/833/131 +f 1878/831/131 1880/833/131 1881/834/131 +f 3680/835/132 3707/836/132 3697/837/132 +f 3621/838/2 3632/839/2 3728/840/2 +f 3621/838/2 3728/840/2 3734/841/2 +f 3734/841/2 3729/842/2 3633/843/2 +f 3734/841/2 3633/843/2 3621/838/2 +f 3663/844/2 3633/843/2 3729/842/2 +f 3663/844/2 3729/842/2 3710/845/2 +f 3632/839/2 3658/846/2 3705/847/2 +f 3632/839/2 3705/847/2 3728/840/2 +f 1902/848/137 1903/849/137 1904/850/137 +f 1902/848/125 1904/850/125 1905/851/125 +f 3643/852/126 1906/853/126 1907/854/126 +f 3643/852/126 1907/854/126 3656/855/126 +f 1908/856/127 1910/857/127 1911/858/127 +f 3688/859/128 3657/860/128 3664/861/128 +f 3688/859/128 3664/861/128 3683/862/128 +f 1912/863/121 1913/864/121 1914/865/121 +f 1912/863/121 1914/865/121 1915/866/121 +f 3725/867/122 1917/868/122 3715/869/122 +f 1918/870/123 1919/871/123 1920/872/123 +f 1918/870/123 1920/872/123 1921/873/123 +f 3730/874/124 3735/875/124 3726/876/124 +f 3730/874/124 3726/876/124 3723/877/124 +f 1922/878/133 1923/879/133 1924/880/133 +f 1922/878/133 1924/880/133 1925/881/133 +f 3641/882/138 1926/883/138 1927/884/138 +f 3641/882/134 1927/884/134 3649/885/134 +f 1928/886/135 1929/887/135 1930/888/135 +f 1928/886/135 1930/888/135 1931/889/135 +f 3629/890/136 3620/891/136 3640/892/136 +f 3629/890/136 3640/892/136 3642/893/136 +f 1940/894/139 1941/895/139 1942/896/139 +f 1940/894/139 1942/896/139 1943/897/139 +f 1944/898/140 1945/899/140 3638/900/140 +f 1944/898/140 3638/900/140 3704/901/140 +f 1978/902/2 1979/903/2 1980/904/2 +f 1978/902/2 1980/904/2 1981/905/2 +f 1981/905/2 1982/906/2 1983/907/2 +f 1984/908/2 1980/904/2 1979/903/2 +f 1984/908/2 1979/903/2 1985/909/2 +f 1982/906/2 1986/910/2 1987/911/2 +f 1982/906/2 1987/911/2 1983/907/2 +f 1988/912/4 1989/913/4 1990/914/4 +f 1988/912/4 1990/914/4 1991/915/4 +f 1992/916/4 1988/912/4 1991/915/4 +f 1992/916/4 1991/915/4 1993/917/4 +f 1994/918/4 1992/916/4 1993/917/4 +f 1994/918/4 1993/917/4 1995/919/4 +f 1989/913/4 1996/920/4 1997/921/4 +f 1989/913/4 1997/921/4 1990/914/4 +f 2060/922/141 2062/923/141 2063/924/141 +f 4643/925/142 2068/926/142 2069/927/142 +f 4643/925/142 2069/927/142 4650/928/142 +f 2074/929/143 2075/930/143 2076/931/143 +f 2074/929/143 2076/931/143 2077/932/143 +f 2078/933/143 2079/934/143 2077/932/143 +f 2078/933/143 2077/932/143 2076/931/143 +f 2080/935/144 2081/936/144 2082/937/144 +f 2080/935/144 2083/938/144 2084/939/144 +f 2080/935/144 2084/939/144 2085/940/144 +f 2108/941/2 2109/942/2 2110/943/2 +f 2108/941/2 2110/943/2 2111/944/2 +f 2111/944/2 2112/945/2 2113/946/2 +f 2111/944/2 2113/946/2 2108/941/2 +f 2114/947/2 2110/943/2 2109/942/2 +f 2114/947/2 2109/942/2 2115/948/2 +f 2112/945/2 2116/949/2 2117/950/2 +f 2112/945/2 2117/950/2 2113/946/2 +f 2118/951/4 2120/952/4 2121/953/4 +f 2122/954/4 2118/951/4 2121/953/4 +f 2122/954/4 2121/953/4 2123/955/4 +f 2124/956/4 2122/954/4 2123/955/4 +f 2119/957/4 2126/958/4 2127/959/4 +f 2119/957/4 2127/959/4 2120/952/4 +f 2190/960/145 2191/961/145 2192/962/145 +f 2190/960/146 2192/962/146 2193/963/146 +f 3691/964/147 2198/965/147 2199/966/147 +f 3691/964/148 2199/966/148 3672/967/148 +f 2204/968/149 2205/969/149 2206/970/149 +f 2204/968/149 2206/970/149 2207/971/149 +f 2208/972/149 2207/971/149 2206/970/149 +f 2210/973/150 2213/974/150 2214/975/150 +f 2244/976/2 2245/977/2 3665/978/2 +f 2246/979/2 2247/980/2 3580/981/2 +f 2246/979/2 3580/981/2 3583/982/2 +f 2248/983/2 2246/979/2 3583/982/2 +f 2248/983/2 3583/982/2 3624/984/2 +f 2249/985/2 2248/983/2 3624/984/2 +f 2249/985/2 3624/984/2 3666/986/2 +f 2250/987/2 2249/985/2 3666/986/2 +f 2250/987/2 3666/986/2 3677/988/2 +f 2245/977/2 2250/987/2 3677/988/2 +f 2245/977/2 3677/988/2 3665/978/2 +f 2262/989/2 2263/990/2 2264/991/2 +f 2262/989/2 2264/991/2 2265/992/2 +f 2266/993/2 2264/991/2 2263/990/2 +f 2266/993/2 2263/990/2 2267/994/2 +f 225/28/2 2266/993/2 2267/994/2 +f 225/28/2 2267/994/2 226/29/2 +f 2300/995/2 4621/996/2 2301/997/2 +f 2308/998/2 4625/999/2 4647/1000/2 +f 2308/998/2 4647/1000/2 2306/1001/2 +f 2309/1002/2 4622/1003/2 4625/999/2 +f 2309/1002/2 4625/999/2 2308/998/2 +f 2310/1004/2 4618/1005/2 4622/1003/2 +f 2310/1004/2 4622/1003/2 2309/1002/2 +f 2301/997/2 4621/996/2 4618/1005/2 +f 2301/997/2 4618/1005/2 2310/1004/2 +f 4667/1006/151 4669/1007/151 2326/1008/151 +f 4667/1006/151 2326/1008/151 2327/1009/151 +f 2330/1010/2 2331/1011/2 2332/1012/2 +f 2330/1010/2 2332/1012/2 2333/1013/2 +f 2334/1014/2 2335/1015/2 2333/1013/2 +f 2334/1014/2 2333/1013/2 2332/1012/2 +f 229/32/2 228/31/2 2335/1015/2 +f 229/32/2 2335/1015/2 2334/1014/2 +f 2372/1016/2 2373/1017/2 3667/1018/2 +f 2376/1019/2 2377/1020/2 3581/1021/2 +f 2376/1019/2 3581/1021/2 3584/1022/2 +f 2378/1023/2 2376/1019/2 3584/1022/2 +f 2379/1024/2 2378/1023/2 3626/1025/2 +f 2379/1024/2 3626/1025/2 3668/1026/2 +f 2380/1027/2 2379/1024/2 3668/1026/2 +f 2380/1027/2 3668/1026/2 3678/1028/2 +f 2373/1017/2 2380/1027/2 3678/1028/2 +f 2373/1017/2 3678/1028/2 3667/1018/2 +f 2390/1029/2 2391/1030/2 2392/1031/2 +f 2390/1029/2 2392/1031/2 2393/1032/2 +f 2394/1033/2 2392/1031/2 2391/1030/2 +f 231/34/2 2394/1033/2 2395/1034/2 +f 231/34/2 2395/1034/2 232/35/2 +f 2400/1035/2 233/36/2 235/38/2 +f 2400/1035/2 235/38/2 2401/1036/2 +f 2402/1037/4 238/41/4 241/44/4 +f 2402/1037/4 241/44/4 2403/1038/4 +f 2401/1036/2 235/38/2 236/39/2 +f 2401/1036/2 236/39/2 2408/1039/2 +f 2409/1040/4 239/42/4 238/41/4 +f 2409/1040/4 238/41/4 2402/1037/4 +f 2426/1041/2 243/46/2 245/48/2 +f 2426/1041/2 245/48/2 2427/1042/2 +f 2432/1043/4 248/51/4 251/54/4 +f 2432/1043/4 251/54/4 2433/1044/4 +f 2427/1042/2 245/48/2 246/49/2 +f 2427/1042/2 246/49/2 2438/1045/2 +f 2439/1046/4 249/52/4 248/51/4 +f 2439/1046/4 248/51/4 2432/1043/4 +f 2470/1047/2 2471/1048/2 254/57/2 +f 2470/1047/2 254/57/2 253/56/2 +f 2472/1049/4 2473/1050/4 261/64/4 +f 2472/1049/4 261/64/4 258/61/4 +f 2471/1048/2 2478/1051/2 256/59/2 +f 2479/1052/4 2472/1049/4 258/61/4 +f 2479/1052/4 258/61/4 260/63/4 +f 2502/1053/5 2503/1054/5 2504/1055/5 +f 2502/1053/5 2504/1055/5 2505/1056/5 +f 2504/1055/5 2503/1054/5 264/67/5 +f 2504/1055/5 264/67/5 263/66/5 +f 265/68/5 264/67/5 2506/1057/5 +f 265/68/5 2506/1057/5 2507/1058/5 +f 2508/1059/6 2509/1060/6 2510/1061/6 +f 2508/1059/6 2510/1061/6 2511/1062/6 +f 2510/1061/6 266/69/6 268/71/6 +f 2510/1061/6 268/71/6 2511/1062/6 +f 267/70/6 2512/1063/6 2513/1064/6 +f 267/70/6 2513/1064/6 268/71/6 +f 2518/1065/152 3749/1066/152 3778/1067/152 +f 2518/1065/152 3778/1067/152 2519/1068/152 +f 3750/1069/153 2522/1070/153 2523/1071/153 +f 3750/1069/153 2523/1071/153 3779/1072/153 +f 3748/1073/4 2524/1074/4 2525/1075/4 +f 3748/1073/4 2525/1075/4 3777/1076/4 +f 2558/1077/1 2559/1078/1 2560/1079/1 +f 2558/1077/1 2560/1079/1 2561/1080/1 +f 3718/1081/58 3877/1082/58 2564/1083/58 +f 3718/1081/58 2564/1083/58 2565/1084/58 +f 2570/1085/5 2571/1086/5 2572/1087/5 +f 2570/1085/5 2572/1087/5 2573/1088/5 +f 2574/1089/6 2575/1090/6 3721/1091/6 +f 2574/1089/6 3721/1091/6 3719/1092/6 +f 2564/1083/58 3877/1082/58 4076/1093/58 +f 2564/1083/58 4076/1093/58 2576/1094/58 +f 2560/1079/1 2559/1078/1 2579/1095/1 +f 2560/1079/1 2579/1095/1 4078/1096/1 +f 2580/1097/5 2581/1098/5 2582/1099/5 +f 2580/1097/5 2582/1099/5 2583/1100/5 +f 2582/1099/5 2581/1098/5 270/73/5 +f 2582/1099/5 270/73/5 269/72/5 +f 271/74/5 270/73/5 2584/1101/5 +f 271/74/5 2584/1101/5 2585/1102/5 +f 2586/1103/6 2587/1104/6 2588/1105/6 +f 2586/1103/6 2588/1105/6 2589/1106/6 +f 2588/1105/6 272/75/6 274/77/6 +f 2588/1105/6 274/77/6 2589/1106/6 +f 273/76/6 2590/1107/6 2591/1108/6 +f 273/76/6 2591/1108/6 274/77/6 +f 2596/1109/152 4023/1110/152 4060/1111/152 +f 2596/1109/152 4060/1111/152 2597/1112/152 +f 4024/1113/153 2600/1114/153 2601/1115/153 +f 4024/1113/153 2601/1115/153 4061/1116/153 +f 4022/1117/4 2602/1118/4 2603/1119/4 +f 4022/1117/4 2603/1119/4 4059/1120/4 +f 2636/1121/5 2637/1122/5 2638/1123/5 +f 2636/1121/5 2638/1123/5 2639/1124/5 +f 2638/1123/5 2637/1122/5 276/79/5 +f 2638/1123/5 276/79/5 275/78/5 +f 277/80/5 276/79/5 2640/1125/5 +f 2642/1126/6 2643/1127/6 2644/1128/6 +f 2642/1126/6 2644/1128/6 2645/1129/6 +f 2644/1128/6 278/81/6 280/83/6 +f 2644/1128/6 280/83/6 2645/1129/6 +f 279/82/6 2647/1130/6 280/83/6 +f 2652/1131/154 4518/1132/154 4534/1133/154 +f 2652/1131/154 4534/1133/154 2653/1134/154 +f 4519/1135/155 2656/1136/155 2657/1137/155 +f 4519/1135/155 2657/1137/155 4535/1138/155 +f 4517/1139/4 2658/1140/4 2659/1141/4 +f 4517/1139/4 2659/1141/4 4533/1142/4 +f 2692/1143/1 2693/1144/1 2694/1145/1 +f 2692/1143/1 2694/1145/1 2695/1146/1 +f 4505/1147/58 4563/1148/58 2698/1149/58 +f 4505/1147/58 2698/1149/58 2699/1150/58 +f 2704/1151/5 2705/1152/5 2706/1153/5 +f 2704/1151/5 2706/1153/5 2707/1154/5 +f 2708/1155/6 2709/1156/6 4508/1157/6 +f 2708/1155/6 4508/1157/6 4506/1158/6 +f 2698/1149/58 4563/1148/58 4611/1159/58 +f 2698/1149/58 4611/1159/58 2710/1160/58 +f 2694/1145/1 2693/1144/1 2713/1161/1 +f 2694/1145/1 2713/1161/1 4613/1162/1 +f 2714/1163/5 2715/1164/5 2716/1165/5 +f 2714/1163/5 2716/1165/5 2717/1166/5 +f 2716/1165/5 2715/1164/5 282/85/5 +f 2716/1165/5 282/85/5 281/84/5 +f 283/86/5 282/85/5 2718/1167/5 +f 2720/1168/6 2721/1169/6 2722/1170/6 +f 2720/1168/6 2722/1170/6 2723/1171/6 +f 2722/1170/6 284/87/6 286/89/6 +f 2722/1170/6 286/89/6 2723/1171/6 +f 285/88/6 2725/1172/6 286/89/6 +f 4590/1173/155 2734/1174/155 2735/1175/155 +f 4590/1173/155 2735/1175/155 4606/1176/155 +f 4588/1177/4 2736/1178/4 2737/1179/4 +f 4588/1177/4 2737/1179/4 4604/1180/4 +f 2803/1181/7 2804/1182/7 2805/1183/7 +f 2803/1181/7 2805/1183/7 2806/1184/7 +f 2804/1182/7 287/90/7 289/92/7 +f 2804/1182/7 289/92/7 2805/1183/7 +f 292/95/7 2803/1181/7 2806/1184/7 +f 292/95/7 2806/1184/7 290/93/7 +f 2840/1185/8 2841/1186/8 2842/1187/8 +f 2840/1185/8 2842/1187/8 2843/1188/8 +f 2841/1186/8 293/96/8 295/98/8 +f 2841/1186/8 295/98/8 2842/1187/8 +f 298/101/8 2840/1185/8 2843/1188/8 +f 298/101/8 2843/1188/8 296/99/8 +f 2844/1189/1 2845/1190/1 2846/1191/1 +f 2844/1189/1 2846/1191/1 2847/1192/1 +f 2848/1193/156 4510/1194/156 2849/1195/156 +f 2848/1193/156 2849/1195/156 2850/1196/156 +f 2852/1197/157 2853/1198/157 2854/1199/157 +f 2852/1197/157 2854/1199/157 2855/1200/157 +f 2856/1201/1 2857/1202/1 2858/1203/1 +f 2860/1204/158 2861/1205/158 2862/1206/158 +f 2860/1204/158 2862/1206/158 4073/1207/158 +f 4079/1208/159 4074/1209/159 4072/1210/159 +f 4079/1208/159 4072/1210/159 2863/1211/159 +f 2864/1212/160 2865/1213/160 2866/1214/160 +f 2864/1212/160 2866/1214/160 2867/1215/160 +f 2868/1216/161 2869/1217/161 2870/1218/161 +f 2868/1216/161 2870/1218/161 2871/1219/161 +f 4088/1220/1 2872/1221/1 2873/1222/1 +f 4088/1220/1 2873/1222/1 4495/1223/1 +f 2944/1224/2 2945/1225/2 2946/1226/2 +f 2944/1224/2 2946/1226/2 2947/1227/2 +f 2947/1227/2 2946/1226/2 2965/1228/2 +f 2947/1227/2 2965/1228/2 2966/1229/2 +f 2971/1230/162 2972/1231/162 4447/1232/162 +f 2973/1233/163 4501/1234/163 4502/1235/163 +f 2978/1236/163 2973/1233/163 4502/1235/163 +f 2978/1236/163 4502/1235/163 4503/1237/163 +f 2975/1238/162 2979/1239/162 2980/1240/162 +f 2975/1238/162 2980/1240/162 2976/1241/162 +f 2982/1242/163 2978/1236/163 4503/1237/163 +f 2982/1242/163 4503/1237/163 4504/1243/163 +f 2988/1244/164 2991/1245/164 2992/1246/164 +f 2988/1244/164 2992/1246/164 4252/1247/164 +f 2994/1248/165 4254/1249/165 4368/1250/165 +f 2991/1245/164 2995/1251/164 2996/1252/164 +f 2998/1253/165 2994/1248/165 4368/1250/165 +f 2998/1253/165 4368/1250/165 4445/1254/165 +f 3003/1255/2 3004/1256/2 3005/1257/2 +f 3003/1255/2 3005/1257/2 3006/1258/2 +f 3006/1258/2 3005/1257/2 3024/1259/2 +f 3006/1258/2 3024/1259/2 3025/1260/2 +f 3030/1261/5 3031/1262/5 4096/1263/5 +f 3030/1261/5 4096/1263/5 4095/1264/5 +f 3032/1265/6 4057/1266/6 4058/1267/6 +f 3031/1262/5 3034/1268/5 3035/1269/5 +f 3031/1262/5 3035/1269/5 4096/1263/5 +f 3037/1270/6 3032/1265/6 4058/1267/6 +f 3037/1270/6 4058/1267/6 4056/1271/6 +f 3046/1272/2 3047/1273/2 3048/1274/2 +f 3046/1272/2 3048/1274/2 3049/1275/2 +f 3049/1275/2 3048/1274/2 3067/1276/2 +f 3049/1275/2 3067/1276/2 3068/1277/2 +f 3073/1278/166 3074/1279/166 4318/1280/166 +f 3073/1278/166 4318/1280/166 4452/1281/166 +f 3075/1282/167 3076/1283/167 4446/1284/167 +f 3075/1282/167 4446/1284/167 4316/1285/167 +f 3074/1279/166 3077/1286/166 3078/1287/166 +f 3074/1279/166 3078/1287/166 4318/1280/166 +f 3080/1288/167 4316/1285/167 4186/1289/167 +f 3077/1286/166 3081/1290/166 3082/1291/166 +f 3077/1286/166 3082/1291/166 3078/1287/166 +f 3084/1292/167 3080/1288/167 4186/1289/167 +f 3084/1292/167 4186/1289/167 4091/1293/167 +f 3089/1294/2 3090/1295/2 3091/1296/2 +f 3089/1294/2 3091/1296/2 3092/1297/2 +f 3092/1297/2 3091/1296/2 3110/1298/2 +f 3092/1297/2 3110/1298/2 3111/1299/2 +f 3160/1300/168 3161/1301/168 3162/1302/168 +f 3160/1300/168 3162/1302/168 3163/1303/168 +f 3164/1304/168 3160/1300/168 3163/1303/168 +f 3164/1304/168 3163/1303/168 3165/1305/168 +f 3166/1306/168 3164/1304/168 3165/1305/168 +f 3166/1306/168 3165/1305/168 3167/1307/168 +f 3166/1306/168 3167/1307/168 3168/1308/168 +f 3166/1306/168 3168/1308/168 3169/1309/168 +f 3161/1301/168 3171/1310/168 3162/1302/168 +f 3172/1311/169 3173/1312/169 3174/1313/169 +f 3172/1311/169 3174/1313/169 3175/1314/169 +f 3774/1315/170 3177/1316/170 3932/1317/170 +f 3178/1318/171 3179/1319/171 3180/1320/171 +f 3178/1318/171 3180/1320/171 3181/1321/171 +f 3812/1322/172 3799/1323/172 3961/1324/172 +f 3812/1322/173 3961/1324/173 3980/1325/173 +f 3190/1326/174 3191/1327/174 3192/1328/174 +f 3906/1329/175 3194/1330/175 3195/1331/175 +f 3906/1329/175 3195/1331/175 3765/1332/175 +f 3196/1333/176 3197/1334/176 3198/1335/176 +f 3196/1333/176 3198/1335/176 3199/1336/176 +f 3953/1337/177 3775/1338/177 3795/1339/177 +f 3222/1340/178 3223/1341/178 3224/1342/178 +f 3226/1343/178 3227/1344/178 3224/1342/178 +f 3226/1343/178 3224/1342/178 3223/1341/178 +f 3228/1345/179 3229/1346/179 3230/1347/179 +f 3228/1345/179 3231/1348/179 3232/1349/179 +f 3228/1345/179 3232/1349/179 3233/1350/179 +f 3234/1351/180 3235/1352/180 3236/1353/180 +f 3234/1351/180 3236/1353/180 3237/1354/180 +f 3238/1355/181 3767/1356/181 3754/1357/181 +f 3238/1355/181 3754/1357/181 3239/1358/181 +f 3240/1359/182 3764/1360/182 3741/1361/182 +f 3240/1359/182 3741/1361/182 3241/1362/182 +f 3242/1363/183 3752/1364/183 3736/1365/183 +f 3242/1363/184 3736/1365/184 3243/1366/184 +f 3244/1367/185 3753/1368/185 3737/1369/185 +f 3244/1367/185 3737/1369/185 3245/1370/185 +f 3768/1371/186 3246/1372/186 3247/1373/186 +f 3768/1371/186 3247/1373/186 3755/1374/186 +f 3248/1375/187 3250/1376/187 3251/1377/187 +f 3252/1378/188 3253/1379/188 3254/1380/188 +f 3252/1378/188 3254/1380/188 3255/1381/188 +f 3256/1382/189 3803/1383/189 3760/1384/189 +f 3256/1382/189 3760/1384/189 3257/1385/189 +f 3258/1386/190 3791/1387/190 3744/1388/190 +f 3258/1386/190 3744/1388/190 3259/1389/190 +f 3260/1390/191 3793/1391/191 3746/1392/191 +f 3260/1390/191 3746/1392/191 3261/1393/191 +f 3819/1394/192 3805/1395/192 3762/1396/192 +f 3819/1394/192 3762/1396/192 3772/1397/192 +f 3347/1398/193 3348/1399/193 3349/1400/193 +f 3347/1398/193 3349/1400/193 3350/1401/193 +f 3351/1402/193 3347/1398/193 3350/1401/193 +f 3351/1402/193 3350/1401/193 3352/1403/193 +f 3353/1404/193 3351/1402/193 3352/1403/193 +f 3353/1404/193 3352/1403/193 3354/1405/193 +f 3348/1399/193 3355/1406/193 3356/1407/193 +f 3401/1408/194 3402/1409/194 3403/1410/194 +f 3401/1408/194 3403/1410/194 3404/1411/194 +f 4157/1412/195 3411/1413/195 3412/1414/195 +f 4157/1412/195 3412/1414/195 4140/1415/195 +f 3415/1416/196 3416/1417/196 3417/1418/196 +f 3415/1416/196 3417/1418/196 3418/1419/196 +f 3419/1420/197 3420/1421/197 3418/1419/197 +f 3419/1420/196 3418/1419/196 3417/1418/196 +f 3421/1422/198 3423/1423/198 3424/1424/198 +f 3421/1422/199 3424/1424/199 3425/1425/199 +f 3421/1422/198 3425/1425/198 3426/1426/198 +f 3449/1427/200 3450/1428/200 3451/1429/200 +f 3449/1427/200 3451/1429/200 3452/1430/200 +f 3453/1431/200 3454/1432/200 3450/1428/200 +f 3453/1431/200 3450/1428/200 3449/1427/200 +f 3455/1433/200 3454/1432/200 3453/1431/200 +f 3452/1430/200 3451/1429/200 3457/1434/200 +f 3452/1430/200 3457/1434/200 3458/1435/200 +f 3503/1436/201 3504/1437/201 3505/1438/201 +f 3503/1436/201 3505/1438/201 3506/1439/201 +f 4403/1440/202 4416/1441/202 3513/1442/202 +f 4403/1440/202 3513/1442/202 3514/1443/202 +f 3517/1444/203 3518/1445/203 3519/1446/203 +f 3517/1444/204 3519/1446/204 3520/1447/204 +f 3521/1448/203 3519/1446/203 3518/1445/203 +f 3521/1448/204 3518/1445/204 3522/1449/204 +f 3523/1450/205 3525/1451/205 3526/1452/205 +f 3523/1450/205 3527/1453/205 3528/1454/205 +f 3523/1450/206 3528/1454/206 3524/1455/206 +f 3529/1456/207 3531/1457/207 3532/1458/207 +f 3530/1459/207 3533/1460/207 3534/1461/207 +f 3530/1459/207 3534/1461/207 3531/1457/207 +f 3535/1462/208 3536/1463/208 3537/1464/208 +f 3535/1462/208 3537/1464/208 3538/1465/208 +f 3538/1465/208 3537/1464/208 3539/1466/208 +f 3538/1465/208 3539/1466/208 3540/1467/208 +f 3540/1467/208 3539/1466/208 320/1468/209 +f 3541/1469/210 3542/1470/210 3543/1471/210 +f 3542/1470/210 3545/1472/210 3546/1473/210 +f 3542/1470/210 3546/1473/210 3543/1471/210 +f 3547/1474/211 3549/1475/211 3550/1476/211 +f 3550/1476/211 3551/1477/211 3552/1478/211 +f 3552/1478/211 3551/1477/211 326/1479/212 +f 3553/1480/10 3554/1481/10 3555/1482/10 +f 3553/1480/10 3555/1482/10 3556/1483/10 +f 3554/1481/213 328/105/213 330/107/213 +f 3554/1481/10 330/107/10 3555/1482/10 +f 3557/1484/214 3558/1485/214 3559/1486/214 +f 3557/1484/214 3559/1486/214 3560/1487/214 +f 3560/1487/214 3559/1486/214 332/1488/214 +f 3560/1487/215 332/1488/215 331/1489/215 +f 3561/1490/11 3562/1491/11 3563/1492/11 +f 3561/1490/11 3563/1492/11 3564/1493/11 +f 3562/1491/216 334/108/216 336/110/216 +f 3562/1491/11 336/110/11 3563/1492/11 +f 3565/1494/217 3566/1495/217 3567/1496/217 +f 3565/1494/12 3567/1496/12 3568/1497/12 +f 3568/1497/217 3567/1496/217 338/112/217 +f 3568/1497/218 338/112/218 337/111/218 +f 3569/1498/2 3570/1499/2 3571/1500/2 +f 3569/1498/2 3571/1500/2 3572/1501/2 +s 1 +f 6/1502/219 7/1503/220 8/1504/221 +f 9/1505/222 10/1506/223 11/1507/224 +f 12/1508/225 13/1509/226 14/1510/227 +f 15/1511/228 16/1512/229 17/1513/230 +f 18/1514/231 19/1515/232 20/1516/233 +f 21/1517/234 22/1518/234 23/1519/234 +f 24/1520/235 25/1521/236 26/1522/237 +f 27/1523/238 28/1524/239 29/1525/240 +f 30/1526/241 31/1527/242 32/1528/243 +f 33/1529/244 31/1527/242 30/1526/241 +f 34/1530/245 35/1531/246 36/1532/247 +f 37/1533/248 38/1534/249 39/1535/250 +f 40/1536/251 37/1533/248 39/1535/250 +f 41/1537/252 42/1538/253 43/1539/254 +f 47/352/2 48/351/2 49/368/2 +f 62/1540/255 63/1541/256 64/1542/257 +f 65/1543/258 66/1544/259 67/1545/260 +f 68/1546/261 69/1547/262 70/1548/263 +f 71/1549/264 72/1550/265 73/1551/266 +f 74/1552/267 75/1553/268 76/1554/269 +f 77/1555/270 78/1556/271 79/1557/272 +f 80/1558/273 81/1559/274 82/1560/275 +f 83/1561/276 84/1562/277 85/1563/278 +f 86/1564/279 87/1565/280 88/1566/281 +f 89/1567/282 90/1568/283 91/1569/284 +f 92/1570/285 93/1571/286 94/1572/287 +f 95/1573/288 96/1574/289 97/1575/290 +f 104/1576/291 105/1577/292 106/1578/293 +f 107/1579/294 108/1580/295 109/1581/296 +f 108/1580/295 110/1582/297 109/1581/296 +f 110/1582/297 111/1583/298 109/1581/296 +f 111/1583/298 112/1584/299 109/1581/296 +f 112/1584/299 113/1585/300 109/1581/296 +f 113/1585/300 114/1586/301 109/1581/296 +f 114/1586/301 115/1587/302 109/1581/296 +f 115/1587/302 116/1588/303 109/1581/296 +f 116/1588/303 117/1589/304 109/1581/296 +f 117/1589/304 118/1590/305 109/1581/296 +f 118/1590/305 119/1591/306 109/1581/296 +f 119/1591/306 120/1592/307 109/1581/296 +f 120/1592/307 107/1579/294 109/1581/296 +f 121/1593/308 122/1594/309 123/1595/310 +f 122/1594/309 124/1596/311 123/1595/310 +f 124/1596/311 125/1597/312 123/1595/310 +f 125/1597/312 126/1598/313 123/1595/310 +f 126/1598/313 127/1599/314 123/1595/310 +f 128/1600/315 129/1601/316 123/1595/310 +f 129/1601/316 130/1602/317 123/1595/310 +f 130/1602/317 131/1603/318 123/1595/310 +f 131/1603/318 132/1604/319 123/1595/310 +f 132/1604/319 133/1605/320 123/1595/310 +f 133/1605/320 134/1606/321 123/1595/310 +f 134/1606/321 121/1593/308 123/1595/310 +f 135/1607/322 136/1608/323 137/1609/324 +f 137/1609/324 136/1608/323 138/1610/325 +f 139/1611/326 140/1612/327 141/1613/328 +f 141/1613/328 140/1612/327 142/1614/329 +f 143/1615/330 137/1609/324 138/1610/325 +f 139/1611/326 141/1613/328 144/1616/331 +f 145/1617/332 146/1618/333 147/1619/334 +f 147/1619/334 146/1618/333 148/1620/335 +f 148/1620/335 146/1618/333 149/1621/336 +f 150/1622/337 151/1623/338 152/1624/339 +f 150/1622/337 153/1625/340 151/1623/338 +f 151/1623/338 153/1625/340 154/1626/341 +f 155/1627/342 156/1628/343 157/1629/344 +f 157/1629/344 156/1628/343 158/1630/345 +f 159/1631/346 160/1632/347 161/1633/348 +f 161/1633/348 160/1632/347 162/1634/349 +f 163/1635/350 157/1629/344 158/1630/345 +f 159/1631/346 161/1633/348 164/1636/351 +f 165/1637/352 166/1638/353 167/1639/354 +f 168/1640/355 165/1637/352 167/1639/354 +f 169/1641/356 168/1640/355 167/1639/354 +f 170/1642/357 169/1641/356 167/1639/354 +f 171/1643/358 170/1642/357 167/1639/354 +f 166/1638/353 171/1643/358 167/1639/354 +f 172/1644/359 173/1645/360 174/1646/361 +f 173/1645/360 175/1647/362 174/1646/361 +f 175/1647/362 176/1648/363 174/1646/361 +f 176/1648/363 177/1649/364 174/1646/361 +f 177/1649/364 178/1650/365 174/1646/361 +f 178/1650/365 172/1644/359 174/1646/361 +f 179/1651/366 180/1652/367 181/1653/368 +f 182/1654/369 179/1651/366 181/1653/368 +f 183/1655/370 182/1654/369 181/1653/368 +f 184/1656/371 183/1655/370 181/1653/368 +f 185/1657/372 184/1656/371 181/1653/368 +f 180/1652/367 185/1657/372 181/1653/368 +f 186/1658/373 187/1659/374 188/1660/375 +f 187/1659/374 189/1661/376 188/1660/375 +f 189/1661/376 190/1662/377 188/1660/375 +f 190/1662/377 191/1663/378 188/1660/375 +f 191/1663/378 192/1664/379 188/1660/375 +f 192/1664/379 186/1658/373 188/1660/375 +f 193/1665/380 194/1666/381 195/1667/382 +f 196/1668/383 197/1669/384 198/1670/385 +f 199/1671/386 200/1672/387 201/1673/388 +f 202/1674/389 203/1675/390 204/1676/2 +f 203/1675/390 205/1677/391 204/1676/2 +f 205/1677/391 206/1678/392 204/1676/2 +f 206/1678/392 207/1679/393 204/1676/2 +f 207/1679/393 208/1680/394 204/1676/2 +f 208/1680/394 209/1681/395 204/1676/2 +f 209/1681/395 210/1682/396 204/1676/2 +f 210/1682/396 211/1683/397 204/1676/2 +f 211/1683/397 212/1684/398 204/1676/2 +f 212/1684/398 202/1674/389 204/1676/2 +f 213/1685/399 214/1686/400 215/1687/2 +f 214/1686/400 216/1688/401 215/1687/2 +f 216/1688/401 217/1689/402 215/1687/2 +f 217/1689/402 218/1690/403 215/1687/2 +f 218/1690/403 219/1691/404 215/1687/2 +f 219/1691/404 220/1692/405 215/1687/2 +f 220/1692/405 221/1693/406 215/1687/2 +f 221/1693/406 222/1694/407 215/1687/2 +f 222/1694/407 223/1695/408 215/1687/2 +f 223/1695/408 213/1685/399 215/1687/2 +f 299/1696/409 300/1697/410 301/1698/411 +f 300/1697/410 302/1699/412 301/1698/411 +f 302/1699/412 303/1700/413 301/1698/411 +f 303/1700/413 304/1701/414 301/1698/411 +f 304/1701/414 305/1702/415 301/1698/411 +f 305/1702/415 306/1703/416 301/1698/411 +f 306/1703/416 307/1704/417 301/1698/411 +f 307/1704/417 308/1705/418 301/1698/411 +f 308/1705/418 309/1706/419 301/1698/411 +f 309/1706/419 299/1696/409 301/1698/411 +f 310/1707/420 311/1708/420 312/1709/420 +f 316/1710/421 317/1711/422 318/1712/423 +f 319/1713/424 320/1468/425 321/1714/426 +f 322/1715/427 323/1716/428 324/1717/429 +f 325/1718/430 326/1479/431 327/1719/432 +f 331/1489/214 332/1488/214 333/1720/214 +f 340/1721/433 341/1722/433 342/1723/433 +f 340/1721/433 342/1723/433 343/1724/433 +f 354/130/1 356/132/1 357/133/1 +f 358/1725/2 4453/1726/2 4118/1727/2 +f 358/1725/2 4118/1727/2 359/1728/2 +f 353/129/1 364/138/1 365/1729/1 +f 370/1730/434 371/1731/435 372/1732/436 +f 370/1730/434 372/1732/436 373/1733/437 +f 3588/1734/438 374/1735/438 375/1736/439 +f 3588/1734/438 375/1736/439 3586/1737/440 +f 376/1738/441 3587/1739/441 3585/1740/442 +f 376/1738/441 3585/1740/442 377/1741/443 +f 3586/1737/440 375/1736/439 378/1742/444 +f 3586/1737/440 378/1742/444 379/1743/445 +f 3585/1740/442 380/1744/446 381/1745/447 +f 3585/1740/442 381/1745/447 377/1741/443 +f 372/1732/436 3590/1746/448 3589/1747/449 +f 372/1732/436 3589/1747/449 373/1733/437 +f 382/1748/450 383/1749/451 384/1750/452 +f 382/1748/450 384/1750/452 385/1751/453 +f 386/1752/454 387/1753/455 388/1754/456 +f 386/1752/454 388/1754/456 389/1755/457 +f 3591/1756/458 3594/1757/459 3593/1758/460 +f 3591/1756/458 3593/1758/460 3592/1759/461 +f 379/1743/445 378/1742/444 383/1749/451 +f 379/1743/445 383/1749/451 382/1748/450 +f 380/1744/446 386/1752/454 389/1755/457 +f 380/1744/446 389/1755/457 381/1745/447 +f 3590/1746/448 3591/1756/458 3592/1759/461 +f 3590/1746/448 3592/1759/461 3589/1747/449 +f 390/1760/462 391/1761/463 392/1762/464 +f 390/1760/462 392/1762/464 393/1763/462 +f 394/1764/465 395/1765/465 396/1766/466 +f 394/1764/465 396/1766/466 397/1767/467 +f 391/1761/463 400/1768/468 401/1769/469 +f 391/1761/463 401/1769/469 392/1762/464 +f 397/1767/467 396/1766/466 402/1770/470 +f 397/1767/467 402/1770/470 403/1771/471 +f 400/1768/468 405/1772/472 406/1773/473 +f 400/1768/468 406/1773/473 401/1769/469 +f 403/1771/471 402/1770/470 407/1774/474 +f 403/1771/471 407/1774/474 408/1775/475 +f 405/1772/472 410/1776/476 411/1777/476 +f 405/1772/472 411/1777/476 406/1773/473 +f 408/1775/475 407/1774/474 412/1778/477 +f 408/1775/475 412/1778/477 413/1779/477 +f 3603/150/5 409/149/5 414/151/5 +f 415/1780/478 416/1781/479 417/1782/480 +f 415/1780/478 417/1782/480 418/1783/481 +f 419/1784/482 420/1785/483 421/1786/482 +f 419/1784/482 421/1786/482 422/1787/484 +f 4194/1788/485 4192/1789/486 425/1790/485 +f 4194/1788/485 425/1790/485 426/1791/487 +f 4215/1792/488 4202/1793/489 427/1794/488 +f 4215/1792/488 427/1794/488 428/1795/490 +f 429/161/18 430/1796/18 431/162/18 +f 435/1797/491 436/1798/492 437/1799/493 +f 435/1797/491 437/1799/493 438/1800/494 +f 439/1801/495 440/1802/496 441/1803/497 +f 439/1801/495 441/1803/497 442/1804/498 +f 438/1800/494 437/1799/493 443/1805/499 +f 438/1800/494 443/1805/499 444/1806/500 +f 444/1806/500 443/1805/499 440/1802/496 +f 444/1806/500 440/1802/496 439/1801/495 +f 445/1807/501 8/1504/221 7/1503/220 +f 445/1807/501 7/1503/220 446/1808/502 +f 10/1506/223 445/1807/501 446/1808/502 +f 10/1506/223 446/1808/502 11/1507/224 +f 436/1798/492 435/1797/491 4242/1809/503 +f 436/1798/492 4242/1809/503 4233/1810/504 +f 442/1804/498 441/1803/497 4116/1811/505 +f 442/1804/498 4116/1811/505 4100/1812/506 +f 6/1502/219 436/1798/492 4233/1810/504 +f 437/1799/493 436/1798/492 6/1502/219 +f 437/1799/493 6/1502/219 8/1504/221 +f 443/1805/499 437/1799/493 8/1504/221 +f 443/1805/499 8/1504/221 445/1807/501 +f 440/1802/496 443/1805/499 445/1807/501 +f 440/1802/496 445/1807/501 10/1506/223 +f 441/1803/497 440/1802/496 10/1506/223 +f 441/1803/497 10/1506/223 9/1505/222 +f 4116/1811/505 441/1803/497 9/1505/222 +f 4116/1811/505 9/1505/222 448/1813/507 +f 4196/1814/508 4177/1815/509 449/1816/510 +f 4196/1814/508 449/1816/510 450/1817/511 +f 4117/1818/512 4104/1819/513 451/1820/514 +f 4117/1818/512 451/1820/514 452/1821/515 +f 4177/1815/509 4134/1822/516 453/1823/517 +f 4177/1815/509 453/1823/517 449/1816/510 +f 4134/1822/516 4117/1818/512 452/1821/515 +f 4134/1822/516 452/1821/515 453/1823/517 +f 454/1824/518 455/1825/519 14/1510/227 +f 454/1824/518 14/1510/227 13/1509/226 +f 17/1513/230 16/1512/229 455/1825/519 +f 17/1513/230 455/1825/519 454/1824/518 +f 450/1817/511 456/1826/520 4243/1827/521 +f 450/1817/511 4243/1827/521 4196/1814/508 +f 4104/1819/513 4101/1828/522 457/1829/523 +f 4104/1819/513 457/1829/523 451/1820/514 +f 12/1508/225 456/1826/520 450/1817/511 +f 449/1816/510 13/1509/226 12/1508/225 +f 449/1816/510 12/1508/225 450/1817/511 +f 453/1823/517 454/1824/518 13/1509/226 +f 453/1823/517 13/1509/226 449/1816/510 +f 452/1821/515 17/1513/230 454/1824/518 +f 452/1821/515 454/1824/518 453/1823/517 +f 451/1820/514 15/1511/228 17/1513/230 +f 451/1820/514 17/1513/230 452/1821/515 +f 457/1829/523 459/1830/524 15/1511/228 +f 457/1829/523 15/1511/228 451/1820/514 +f 460/1831/525 461/1832/526 4208/1833/526 +f 460/1831/525 4208/1833/526 4201/1834/527 +f 19/1515/232 464/1835/528 4216/1836/529 +f 19/1515/232 4216/1836/529 20/1516/233 +f 464/1835/528 460/1831/525 4201/1834/527 +f 464/1835/528 4201/1834/527 4216/1836/529 +f 4126/1837/234 21/1517/234 23/1519/234 +f 4126/1837/234 23/1519/234 4128/1838/234 +f 466/180/23 468/182/23 469/1839/23 +f 470/183/24 4473/185/24 471/1840/24 +f 486/1841/530 487/1842/531 488/1843/532 +f 486/1841/530 488/1843/532 489/1844/533 +f 4148/1845/534 490/1846/535 491/1847/536 +f 4148/1845/534 491/1847/536 4176/1848/537 +f 4142/1849/538 492/1850/539 493/1851/540 +f 4142/1849/538 493/1851/540 4173/1852/541 +f 494/1853/542 495/1854/543 496/1855/544 +f 494/1853/542 496/1855/544 497/1856/545 +f 4141/1857/546 498/1858/547 499/1859/548 +f 4141/1857/546 499/1859/548 4172/1860/549 +f 4147/1861/550 4149/1862/551 4182/1863/552 +f 4147/1861/550 4182/1863/552 4175/1864/553 +f 500/1865/554 501/1866/555 502/1867/556 +f 500/1865/554 502/1867/556 503/1868/557 +f 4198/1869/558 504/1870/559 505/1871/560 +f 4198/1869/558 505/1871/560 4237/1872/561 +f 4189/1873/562 506/1874/563 507/1875/564 +f 4189/1873/562 507/1875/564 4221/1876/565 +f 508/1877/566 509/1878/567 510/1879/568 +f 508/1877/566 510/1879/568 511/1880/569 +f 4188/1881/570 512/1882/571 513/1883/572 +f 4188/1881/570 513/1883/572 4223/1884/573 +f 4197/1885/574 514/1886/575 515/1887/576 +f 4197/1885/574 515/1887/576 4235/1888/577 +f 516/1889/578 517/1890/579 518/1891/580 +f 516/1889/578 518/1891/580 519/1892/580 +f 4333/1893/581 520/1894/582 521/1895/583 +f 4333/1893/581 521/1895/583 4352/1896/583 +f 4331/1897/584 522/1898/585 523/1899/586 +f 4331/1897/584 523/1899/586 4348/1900/586 +f 524/1901/587 525/1902/588 526/1903/589 +f 524/1901/587 526/1903/589 527/1904/589 +f 4330/1905/590 528/1906/591 529/1907/592 +f 4330/1905/590 529/1907/592 4347/1908/592 +f 4332/1909/593 4334/1910/594 4357/1911/595 +f 4332/1909/593 4357/1911/595 4351/1912/595 +f 530/1913/596 531/1914/597 532/1915/598 +f 530/1913/596 532/1915/598 533/1916/599 +f 4386/1917/600 534/1918/601 535/1919/602 +f 4386/1917/600 535/1919/602 4422/1920/603 +f 536/1921/604 537/1922/605 538/1923/606 +f 536/1921/604 538/1923/606 539/1924/606 +f 4385/1925/607 540/1926/608 541/1927/609 +f 4385/1925/607 541/1927/609 4420/1928/610 +f 4388/1929/611 542/1930/612 543/1931/613 +f 4388/1929/611 543/1931/613 4433/1932/613 +f 4390/1933/614 4387/1934/615 4431/1935/616 +f 4390/1933/614 4431/1935/616 4437/1936/616 +f 544/1937/617 545/1938/618 546/1939/619 +f 544/1937/617 546/1939/619 547/1940/620 +f 4226/1941/621 548/1942/622 549/1943/623 +f 4226/1941/621 549/1943/623 4258/1944/624 +f 550/1945/625 551/1946/626 552/1947/627 +f 550/1945/625 552/1947/627 553/1948/628 +f 4225/1949/629 554/1950/630 555/1951/631 +f 4225/1949/629 555/1951/631 4257/1952/632 +f 4230/1953/633 556/1954/634 557/1955/635 +f 4230/1953/633 557/1955/635 4261/1956/636 +f 4231/1957/637 4262/1958/638 4264/1959/639 +f 4231/1957/637 4264/1959/639 4241/1960/640 +f 4238/208/30 4222/219/30 564/216/30 +f 568/222/31 569/1961/31 570/223/31 +f 4224/226/30 576/232/30 572/227/30 +f 4180/1962/641 4169/1963/642 504/1870/559 +f 4180/1962/641 504/1870/559 4198/1869/558 +f 4170/1964/643 4163/1965/644 506/1874/563 +f 4170/1964/643 506/1874/563 4189/1873/562 +f 4164/1966/645 4167/1967/646 509/1878/567 +f 4164/1966/645 509/1878/567 508/1877/566 +f 4168/1968/647 4178/1969/648 512/1882/571 +f 4168/1968/647 512/1882/571 4188/1881/570 +f 4179/1970/649 4183/1971/650 514/1886/575 +f 4179/1970/649 514/1886/575 4197/1885/574 +f 4181/1972/651 501/1866/555 500/1865/554 +f 4181/1972/651 500/1865/554 4184/1973/652 +f 582/1974/653 490/1846/535 4148/1845/534 +f 582/1974/653 4148/1845/534 583/1975/654 +f 584/1976/655 492/1850/539 4142/1849/538 +f 584/1976/655 4142/1849/538 4120/1977/656 +f 585/1978/657 495/1854/543 494/1853/542 +f 585/1978/657 494/1853/542 586/1979/658 +f 587/1980/659 498/1858/547 4141/1857/546 +f 587/1980/659 4141/1857/546 4119/1981/660 +f 4132/1982/661 588/1983/662 4149/1862/551 +f 4132/1982/661 4149/1862/551 4147/1861/550 +f 4133/1984/663 487/1842/531 486/1841/530 +f 4133/1984/663 486/1841/530 4144/1985/664 +f 589/239/32 590/1986/32 4460/240/32 +f 4435/254/36 594/1987/36 4469/255/36 +f 595/257/37 4467/259/37 4470/1988/37 +f 596/1989/665 597/1990/666 598/1991/667 +f 596/1989/665 598/1991/667 599/1992/668 +f 4432/1993/669 4423/1994/670 600/1995/671 +f 4432/1993/669 600/1995/671 601/1996/672 +f 599/1992/668 598/1991/667 602/1997/673 +f 599/1992/668 602/1997/673 603/1998/674 +f 4423/1994/670 4418/1999/675 604/2000/676 +f 4423/1994/670 604/2000/676 600/1995/671 +f 605/2001/674 606/2002/673 607/2003/677 +f 605/2001/674 607/2003/677 608/2004/678 +f 4419/2005/675 4421/2006/679 609/2007/680 +f 4419/2005/675 609/2007/680 610/2008/676 +f 608/2004/678 607/2003/677 611/2009/681 +f 608/2004/678 611/2009/681 612/2010/682 +f 4421/2006/679 4434/2011/683 613/2012/684 +f 4421/2006/679 613/2012/684 609/2007/680 +f 612/2010/682 611/2009/681 614/2013/685 +f 612/2010/682 614/2013/685 615/2014/686 +f 4434/2011/683 4438/2015/687 616/2016/688 +f 4434/2011/683 616/2016/688 613/2012/684 +f 615/2014/686 614/2013/685 597/1990/666 +f 615/2014/686 597/1990/666 596/1989/665 +f 4438/2015/687 4432/1993/669 601/1996/672 +f 4438/2015/687 601/1996/672 616/2016/688 +f 617/2017/689 618/2018/690 520/1894/582 +f 617/2017/689 520/1894/582 4333/1893/581 +f 4297/2019/691 619/2020/692 522/1898/585 +f 4297/2019/691 522/1898/585 4331/1897/584 +f 620/2021/693 621/2022/694 525/1902/588 +f 620/2021/693 525/1902/588 524/1901/587 +f 4296/2023/695 622/2024/696 528/1906/591 +f 4296/2023/695 528/1906/591 4330/1905/590 +f 4301/2025/697 623/2026/698 4334/1910/594 +f 4301/2025/697 4334/1910/594 4332/1909/593 +f 4303/2027/699 4302/2028/700 517/1890/579 +f 4303/2027/699 517/1890/579 516/1889/578 +f 4360/2029/701 4355/2030/702 530/1913/596 +f 4360/2029/701 530/1913/596 533/1916/599 +f 4356/2031/703 4349/2032/704 534/1918/601 +f 4356/2031/703 534/1918/601 4386/1917/600 +f 4350/2033/705 4353/2034/706 537/1922/605 +f 4350/2033/705 537/1922/605 536/1921/604 +f 4354/2035/707 4358/2036/708 540/1926/608 +f 4354/2035/707 540/1926/608 4385/1925/607 +f 4359/2037/709 4362/2038/710 542/1930/612 +f 4359/2037/709 542/1930/612 4388/1929/611 +f 4363/2039/711 4361/2040/712 4387/1934/615 +f 4363/2039/711 4387/1934/615 4390/1933/614 +f 547/1940/620 546/1939/619 618/2018/690 +f 547/1940/620 618/2018/690 617/2017/689 +f 4258/1944/624 549/1943/623 619/2020/692 +f 4258/1944/624 619/2020/692 4297/2019/691 +f 553/1948/628 552/1947/627 621/2022/694 +f 553/1948/628 621/2022/694 620/2021/693 +f 4257/1952/632 555/1951/631 622/2024/696 +f 4257/1952/632 622/2024/696 4296/2023/695 +f 4261/1956/636 557/1955/635 623/2026/698 +f 4261/1956/636 623/2026/698 4301/2025/697 +f 4264/1959/639 4262/1958/638 4302/2028/700 +f 4264/1959/639 4302/2028/700 4303/2027/699 +f 624/263/38 625/2041/38 626/2042/38 +f 624/263/38 626/2042/38 627/260/38 +f 630/2043/713 28/1524/239 27/1523/238 +f 630/2043/713 27/1523/238 631/2044/714 +f 4204/2045/715 4295/2046/716 4287/2047/717 +f 4204/2045/715 4287/2047/717 632/2048/718 +f 4203/2049/719 24/1520/235 26/1522/237 +f 4203/2049/719 26/1522/237 633/2050/720 +f 4287/2047/717 4295/2046/716 4337/2051/721 +f 4287/2047/717 4337/2051/721 4338/2052/722 +f 25/1521/236 634/2053/58 635/2054/723 +f 25/1521/236 635/2054/723 26/1522/237 +f 29/1525/240 28/1524/239 636/2055/724 +f 29/1525/240 636/2055/724 637/2056/725 +f 4338/2052/722 4337/2051/721 4383/2057/726 +f 4338/2052/722 4383/2057/726 4382/2058/727 +f 638/264/39 640/266/39 641/2059/39 +f 4154/2060/728 4209/2061/729 642/2062/730 +f 4154/2060/728 642/2062/730 643/2063/731 +f 648/2064/732 649/2065/733 4206/2066/734 +f 648/2064/732 4206/2066/734 4210/2067/732 +f 650/2068/735 651/2069/736 4155/2070/736 +f 650/2068/735 4155/2070/736 4162/2071/737 +f 652/2072/738 650/2068/735 4162/2071/737 +f 652/2072/738 4162/2071/737 4152/2073/738 +f 4206/2066/734 649/2065/733 653/2074/739 +f 4206/2066/734 653/2074/739 4219/2075/739 +f 643/2063/731 642/2062/730 4220/2076/740 +f 643/2063/731 4220/2076/740 4153/2077/741 +f 4373/2078/742 4414/2079/743 658/2080/744 +f 4373/2078/742 658/2080/744 659/2081/745 +f 660/275/43 661/2082/746 662/276/43 +f 664/2083/747 665/2084/748 4406/2085/748 +f 664/2083/747 4406/2085/748 4415/2086/747 +f 666/2087/749 667/2088/750 4374/2089/750 +f 666/2087/749 4374/2089/750 4381/2090/751 +f 659/2081/745 658/2080/744 4410/2091/752 +f 659/2081/745 4410/2091/752 4375/2092/753 +f 668/2093/754 666/2087/749 4381/2090/751 +f 668/2093/754 4381/2090/751 4376/2094/754 +f 4406/2085/748 665/2084/748 669/2095/755 +f 4406/2085/748 669/2095/755 4411/2096/755 +f 670/2097/756 671/2098/757 672/2099/758 +f 670/2097/756 672/2099/758 673/2100/759 +f 4275/2101/760 674/2102/760 675/2103/761 +f 4275/2101/760 675/2103/761 4263/2104/762 +f 676/2105/763 4250/2106/763 4255/2107/764 +f 676/2105/763 4255/2107/764 677/2108/765 +f 673/2100/759 672/2099/758 678/2109/766 +f 673/2100/759 678/2109/766 679/2110/766 +f 677/2108/765 4255/2107/764 4251/2111/767 +f 677/2108/765 4251/2111/767 680/2112/767 +f 4263/2104/762 675/2103/761 681/2113/768 +f 4263/2104/762 681/2113/768 4270/2114/768 +f 682/278/1 684/280/1 685/2115/1 +f 4279/2116/769 690/2117/769 691/2118/769 +f 4279/2116/769 691/2118/769 4292/2119/769 +f 704/309/51 4340/2120/51 4342/310/51 +f 706/2121/770 707/2122/771 708/2123/772 +f 706/2121/770 708/2123/772 709/2124/773 +f 4371/2125/774 710/2126/774 711/2127/775 +f 4371/2125/774 711/2127/775 4369/2128/776 +f 709/2124/773 708/2123/772 712/2129/777 +f 709/2124/773 712/2129/777 713/2130/778 +f 4369/2128/776 711/2127/775 714/2131/779 +f 4369/2128/776 714/2131/779 4372/2132/779 +f 4366/2133/780 4346/2134/781 715/2135/781 +f 4366/2133/780 715/2135/781 716/2136/782 +f 716/2136/782 717/2137/783 4345/2138/783 +f 716/2136/782 4345/2138/783 4366/2133/780 +f 718/2139/784 719/2140/785 720/2141/786 +f 718/2139/784 720/2141/786 721/2142/787 +f 728/2143/788 729/2144/789 730/2145/790 +f 728/2143/788 730/2145/790 731/2146/791 +f 732/2147/792 733/2148/793 734/2149/794 +f 732/2147/792 734/2149/794 735/2150/795 +f 721/2142/787 720/2141/786 4496/2151/796 +f 721/2142/787 4496/2151/796 4412/2152/797 +f 735/2150/795 734/2149/794 736/2153/798 +f 735/2150/795 736/2153/798 737/2154/799 +f 731/2146/791 730/2145/790 738/2155/800 +f 731/2146/791 738/2155/800 739/2156/801 +f 4124/2157/802 744/2158/802 745/2159/803 +f 4124/2157/802 745/2159/803 4121/2160/804 +f 746/2161/805 4097/2162/805 4099/2163/806 +f 746/2161/805 4099/2163/806 747/2164/807 +f 747/2164/807 4099/2163/806 4098/2165/808 +f 747/2164/807 4098/2165/808 750/2166/808 +f 4121/2160/804 745/2159/803 751/2167/809 +f 4121/2160/804 751/2167/809 4125/2168/809 +f 752/326/1 754/328/1 755/2169/1 +f 4424/2170/810 4440/2171/811 756/2172/812 +f 4424/2170/810 756/2172/812 757/2173/810 +f 758/2174/813 759/2175/814 4483/2176/815 +f 758/2174/813 4483/2176/815 4486/2177/813 +f 753/327/1 761/330/1 754/328/1 +f 759/2175/814 762/2178/816 4491/2179/816 +f 759/2175/814 4491/2179/816 4483/2176/815 +f 4440/2171/811 4439/2180/817 763/2181/817 +f 4440/2171/811 763/2181/817 756/2172/812 +f 772/2182/818 773/2183/819 774/2184/820 +f 772/2182/818 774/2184/820 775/2185/821 +f 782/2186/822 783/2187/823 784/2188/824 +f 782/2186/822 784/2188/824 785/2189/825 +f 786/2190/826 787/2191/827 788/2192/828 +f 786/2190/826 788/2192/828 789/2193/829 +f 773/2183/819 4143/2194/830 4084/2195/831 +f 773/2183/819 4084/2195/831 774/2184/820 +f 787/2191/827 790/2196/832 791/2197/833 +f 787/2191/827 791/2197/833 788/2192/828 +f 783/2187/823 792/2198/834 793/2199/835 +f 783/2187/823 793/2199/835 784/2188/824 +f 794/2200/836 31/1527/242 33/1529/244 +f 794/2200/836 33/1529/244 795/2201/837 +f 796/2202/838 797/2203/839 31/1527/242 +f 796/2202/838 31/1527/242 794/2200/836 +f 798/2204/840 799/2205/841 797/2203/839 +f 798/2204/840 797/2203/839 796/2202/838 +f 800/2206/842 34/1530/245 36/1532/247 +f 800/2206/842 36/1532/247 801/2207/843 +f 35/1531/246 802/2208/844 803/2209/845 +f 35/1531/246 803/2209/845 36/1532/247 +f 797/2203/839 799/2205/841 804/2210/846 +f 797/2203/839 804/2210/846 805/2211/847 +f 31/1527/242 797/2203/839 805/2211/847 +f 31/1527/242 805/2211/847 806/2212/848 +f 32/1528/243 31/1527/242 806/2212/848 +f 32/1528/243 806/2212/848 807/2213/849 +f 801/2207/843 36/1532/247 799/2205/841 +f 801/2207/843 799/2205/841 798/2204/840 +f 804/2210/846 799/2205/841 36/1532/247 +f 804/2210/846 36/1532/247 803/2209/845 +f 808/2214/850 809/2215/837 4538/2216/851 +f 808/2214/850 4538/2216/851 39/1535/250 +f 810/2217/852 808/2214/850 39/1535/250 +f 810/2217/852 39/1535/250 811/2218/853 +f 812/2219/854 810/2217/852 811/2218/853 +f 812/2219/854 811/2218/853 813/2220/855 +f 814/2221/856 815/2222/857 42/1538/253 +f 814/2221/856 42/1538/253 41/1537/252 +f 43/1539/254 42/1538/253 816/2223/858 +f 43/1539/254 816/2223/858 817/2224/844 +f 811/2218/853 818/2225/847 819/2226/846 +f 811/2218/853 819/2226/846 813/2220/855 +f 39/1535/250 820/2227/848 818/2225/847 +f 39/1535/250 818/2225/847 811/2218/853 +f 38/1534/249 821/2228/849 820/2227/848 +f 38/1534/249 820/2227/848 39/1535/250 +f 815/2222/857 812/2219/854 813/2220/855 +f 815/2222/857 813/2220/855 42/1538/253 +f 819/2226/846 816/2223/858 42/1538/253 +f 819/2226/846 42/1538/253 813/2220/855 +f 828/365/6 829/2229/6 3577/367/6 +f 44/6/2 52/11/2 45/7/2 +f 56/15/1 855/410/1 854/409/1 +f 57/16/1 861/416/1 58/17/1 +f 59/18/1 866/421/1 867/2230/1 +f 857/412/1 870/424/1 871/425/1 +f 876/2231/859 877/2232/860 878/2233/860 +f 876/2231/859 878/2233/860 879/2234/861 +f 4497/2235/862 882/2236/863 883/2237/864 +f 4497/2235/862 883/2237/864 4498/2238/865 +f 882/2236/863 884/2239/866 885/2240/866 +f 882/2236/863 885/2240/866 883/2237/864 +f 4520/2241/867 890/2242/868 891/2243/869 +f 4520/2241/867 891/2243/869 4521/2244/867 +f 890/2242/868 892/2245/870 893/2246/870 +f 890/2242/868 893/2246/870 891/2243/869 +f 898/2247/871 876/2231/859 879/2234/861 +f 898/2247/871 879/2234/861 899/2248/871 +f 900/2249/872 901/2250/873 902/2251/874 +f 900/2249/872 902/2251/874 903/2252/872 +f 908/2253/875 4085/2254/876 4086/2255/876 +f 908/2253/875 4086/2255/876 909/2256/877 +f 4107/2257/878 910/2258/878 911/2259/878 +f 4107/2257/878 911/2259/878 4108/2260/878 +f 912/2261/879 913/2262/880 914/2263/880 +f 912/2261/879 914/2263/880 915/2264/881 +f 918/2265/882 908/2253/875 909/2256/877 +f 918/2265/882 909/2256/877 919/2266/882 +f 4111/2267/883 912/2261/879 915/2264/881 +f 4111/2267/883 915/2264/881 4112/2268/883 +f 901/2250/873 4304/2269/884 4305/2270/884 +f 901/2250/873 4305/2270/884 902/2251/874 +f 4307/2271/885 4158/2272/885 4159/2273/885 +f 4307/2271/885 4159/2273/885 4308/2274/885 +f 925/467/75 928/2275/75 929/470/75 +f 937/2276/886 938/2277/887 939/2278/888 +f 937/2276/886 939/2278/888 940/2279/889 +f 938/2277/887 941/2280/890 942/2281/891 +f 938/2277/887 942/2281/891 939/2278/888 +f 941/2280/890 943/2282/892 944/2283/893 +f 941/2280/890 944/2283/893 942/2281/891 +f 943/2282/892 945/2284/894 946/2285/895 +f 943/2282/892 946/2285/895 944/2283/893 +f 945/2284/894 947/2286/896 948/2287/897 +f 945/2284/894 948/2287/897 946/2285/895 +f 947/2286/896 949/2288/898 950/2289/899 +f 947/2286/896 950/2289/899 948/2287/897 +f 949/2288/898 951/2290/900 952/2291/901 +f 949/2288/898 952/2291/901 950/2289/899 +f 951/2290/900 953/2292/902 954/2293/903 +f 951/2290/900 954/2293/903 952/2291/901 +f 953/2292/902 955/2294/904 956/2295/905 +f 953/2292/902 956/2295/905 954/2293/903 +f 957/2296/904 958/2297/906 959/2298/907 +f 957/2296/904 959/2298/907 960/2299/905 +f 958/2297/906 961/2300/908 962/2301/909 +f 958/2297/906 962/2301/909 959/2298/907 +f 961/2300/908 963/2302/910 964/2303/911 +f 961/2300/908 964/2303/911 962/2301/909 +f 963/2302/910 937/2276/886 940/2279/889 +f 963/2302/910 940/2279/889 964/2303/911 +f 940/2279/889 939/2278/888 965/2304/912 +f 940/2279/889 965/2304/912 966/2305/913 +f 939/2278/888 942/2281/891 967/2306/914 +f 939/2278/888 967/2306/914 965/2304/912 +f 942/2281/891 944/2283/893 968/2307/915 +f 942/2281/891 968/2307/915 967/2306/914 +f 944/2283/893 946/2285/895 969/2308/916 +f 944/2283/893 969/2308/916 968/2307/915 +f 946/2285/895 948/2287/897 970/2309/917 +f 946/2285/895 970/2309/917 969/2308/916 +f 948/2287/897 950/2289/899 971/2310/918 +f 948/2287/897 971/2310/918 970/2309/917 +f 950/2289/899 952/2291/901 972/2311/919 +f 950/2289/899 972/2311/919 971/2310/918 +f 952/2291/901 954/2293/903 973/2312/920 +f 952/2291/901 973/2312/920 972/2311/919 +f 954/2293/903 956/2295/905 974/2313/921 +f 954/2293/903 974/2313/921 973/2312/920 +f 960/2299/905 959/2298/907 975/2314/922 +f 960/2299/905 975/2314/922 976/2315/923 +f 959/2298/907 962/2301/909 977/2316/924 +f 959/2298/907 977/2316/924 975/2314/922 +f 962/2301/909 964/2303/911 978/2317/925 +f 962/2301/909 978/2317/925 977/2316/924 +f 964/2303/911 940/2279/889 966/2305/913 +f 964/2303/911 966/2305/913 978/2317/925 +f 966/2305/913 965/2304/912 979/2318/926 +f 966/2305/913 979/2318/926 980/2319/927 +f 965/2304/912 967/2306/914 981/2320/928 +f 965/2304/912 981/2320/928 979/2318/926 +f 967/2306/914 968/2307/915 982/2321/929 +f 967/2306/914 982/2321/929 981/2320/928 +f 968/2307/915 969/2308/916 983/2322/930 +f 968/2307/915 983/2322/930 982/2321/929 +f 969/2308/916 970/2309/917 984/2323/931 +f 969/2308/916 984/2323/931 983/2322/930 +f 970/2309/917 971/2310/918 985/2324/932 +f 970/2309/917 985/2324/932 984/2323/931 +f 971/2310/918 972/2311/919 986/2325/933 +f 971/2310/918 986/2325/933 985/2324/932 +f 972/2311/919 973/2312/920 987/2326/934 +f 972/2311/919 987/2326/934 986/2325/933 +f 973/2312/920 974/2313/921 988/2327/935 +f 973/2312/920 988/2327/935 987/2326/934 +f 976/2315/923 975/2314/922 989/2328/936 +f 976/2315/923 989/2328/936 990/2329/935 +f 975/2314/922 977/2316/924 991/2330/937 +f 975/2314/922 991/2330/937 989/2328/936 +f 977/2316/924 978/2317/925 992/2331/938 +f 977/2316/924 992/2331/938 991/2330/937 +f 978/2317/925 966/2305/913 980/2319/927 +f 978/2317/925 980/2319/927 992/2331/938 +f 980/2319/927 979/2318/926 993/2332/939 +f 980/2319/927 993/2332/939 994/2333/940 +f 979/2318/926 981/2320/928 995/2334/941 +f 979/2318/926 995/2334/941 993/2332/939 +f 981/2320/928 982/2321/929 996/2335/942 +f 981/2320/928 996/2335/942 995/2334/941 +f 982/2321/929 983/2322/930 997/2336/943 +f 982/2321/929 997/2336/943 996/2335/942 +f 983/2322/930 984/2323/931 998/2337/944 +f 983/2322/930 998/2337/944 997/2336/943 +f 984/2323/931 985/2324/932 999/2338/945 +f 984/2323/931 999/2338/945 998/2337/944 +f 985/2324/932 986/2325/933 1000/2339/946 +f 985/2324/932 1000/2339/946 999/2338/945 +f 986/2325/933 987/2326/934 1001/2340/947 +f 986/2325/933 1001/2340/947 1000/2339/946 +f 987/2326/934 988/2327/935 1002/2341/948 +f 987/2326/934 1002/2341/948 1001/2340/947 +f 990/2329/935 989/2328/936 1003/2342/949 +f 990/2329/935 1003/2342/949 1004/2343/950 +f 989/2328/936 991/2330/937 1005/2344/951 +f 989/2328/936 1005/2344/951 1003/2342/949 +f 991/2330/937 992/2331/938 1006/2345/952 +f 991/2330/937 1006/2345/952 1005/2344/951 +f 992/2331/938 980/2319/927 994/2333/940 +f 992/2331/938 994/2333/940 1006/2345/952 +f 994/2333/940 993/2332/939 1007/2346/953 +f 994/2333/940 1007/2346/953 1008/2347/954 +f 993/2332/939 995/2334/941 1009/2348/955 +f 993/2332/939 1009/2348/955 1007/2346/953 +f 995/2334/941 996/2335/942 1010/2349/956 +f 995/2334/941 1010/2349/956 1009/2348/955 +f 996/2335/942 997/2336/943 1011/2350/957 +f 996/2335/942 1011/2350/957 1010/2349/956 +f 997/2336/943 998/2337/944 1012/2351/958 +f 997/2336/943 1012/2351/958 1011/2350/957 +f 998/2337/944 999/2338/945 1013/2352/959 +f 998/2337/944 1013/2352/959 1012/2351/958 +f 999/2338/945 1000/2339/946 1014/2353/960 +f 999/2338/945 1014/2353/960 1013/2352/959 +f 1000/2339/946 1001/2340/947 1015/2354/961 +f 1000/2339/946 1015/2354/961 1014/2353/960 +f 1001/2340/947 1002/2341/948 1016/2355/962 +f 1001/2340/947 1016/2355/962 1015/2354/961 +f 1004/2343/950 1003/2342/949 1017/2356/963 +f 1004/2343/950 1017/2356/963 1018/2357/962 +f 1003/2342/949 1005/2344/951 1019/2358/964 +f 1003/2342/949 1019/2358/964 1017/2356/963 +f 1005/2344/951 1006/2345/952 1020/2359/965 +f 1005/2344/951 1020/2359/965 1019/2358/964 +f 1006/2345/952 994/2333/940 1008/2347/954 +f 1006/2345/952 1008/2347/954 1020/2359/965 +f 4271/2360/966 4268/2361/967 4327/2362/967 +f 4271/2360/966 4327/2362/967 4325/2363/968 +f 4271/2360/966 4325/2363/968 4321/2364/969 +f 4271/2360/966 4321/2364/969 4276/2365/970 +f 4276/2365/970 4321/2364/969 4319/2366/971 +f 4276/2365/970 4319/2366/971 4281/2367/972 +f 4281/2367/972 4319/2366/971 4315/2368/973 +f 4281/2367/972 4315/2368/973 4285/2369/974 +f 4285/2369/974 4315/2368/973 4313/2370/975 +f 4285/2369/974 4313/2370/975 4288/2371/976 +f 4288/2371/976 4313/2370/975 1021/2372/977 +f 4288/2371/976 1021/2372/977 1022/2373/978 +f 1022/2373/978 1021/2372/977 4314/2374/979 +f 1022/2373/978 4314/2374/979 4289/2375/980 +f 4289/2375/980 4314/2374/979 4317/2376/981 +f 4289/2375/980 4317/2376/981 4286/2377/982 +f 4286/2377/982 4317/2376/981 4320/2378/983 +f 4286/2377/982 4320/2378/983 4282/2379/984 +f 4282/2379/984 4320/2378/983 4322/2380/985 +f 4282/2379/984 4322/2380/985 4277/2381/986 +f 4277/2381/986 4322/2380/985 4326/2382/987 +f 4277/2381/986 4326/2382/987 4272/2383/988 +f 4272/2383/988 4326/2382/987 4328/2384/989 +f 4272/2383/988 4328/2384/989 4269/2385/989 +f 65/1543/258 68/1546/261 70/1548/263 +f 65/1543/258 70/1548/263 66/1544/259 +f 71/1549/264 62/1540/255 64/1542/257 +f 71/1549/264 64/1542/257 72/1550/265 +f 68/1546/261 74/1552/267 76/1554/269 +f 68/1546/261 76/1554/269 69/1547/262 +f 77/1555/270 71/1549/264 73/1551/266 +f 77/1555/270 73/1551/266 78/1556/271 +f 74/1552/267 1023/2386/990 4312/2387/991 +f 74/1552/267 4312/2387/991 75/1553/268 +f 1024/2388/992 77/1555/270 79/1557/272 +f 1024/2388/992 79/1557/272 4290/2389/993 +f 1023/2386/990 80/1558/273 82/1560/275 +f 1023/2386/990 82/1560/275 4312/2387/991 +f 83/1561/276 1024/2388/992 4290/2389/993 +f 83/1561/276 4290/2389/993 84/1562/277 +f 80/1558/273 86/1564/279 88/1566/281 +f 80/1558/273 88/1566/281 81/1559/274 +f 89/1567/282 83/1561/276 85/1563/278 +f 89/1567/282 85/1563/278 90/1568/283 +f 86/1564/279 92/1570/285 94/1572/287 +f 86/1564/279 94/1572/287 87/1565/280 +f 95/1573/288 89/1567/282 91/1569/284 +f 95/1573/288 91/1569/284 96/1574/289 +f 1025/2390/994 1026/2391/995 1027/2392/996 +f 1025/2390/994 1027/2392/996 1028/2393/997 +f 1026/2391/995 1029/2394/998 1030/2395/999 +f 1026/2391/995 1030/2395/999 1027/2392/996 +f 1029/2394/998 1031/2396/1000 1032/2397/1001 +f 1029/2394/998 1032/2397/1001 1030/2395/999 +f 1031/2396/1000 1033/2398/1002 1034/2399/1003 +f 1031/2396/1000 1034/2399/1003 1032/2397/1001 +f 1035/2400/1002 1036/2401/1004 1037/2402/1005 +f 1035/2400/1002 1037/2402/1005 1038/2403/1003 +f 1036/2401/1004 1039/2404/1006 1040/2405/1007 +f 1036/2401/1004 1040/2405/1007 1037/2402/1005 +f 1039/2404/1006 1041/2406/1008 1042/2407/1009 +f 1039/2404/1006 1042/2407/1009 1040/2405/1007 +f 1041/2406/1008 1043/2408/1010 1044/2409/1011 +f 1041/2406/1008 1044/2409/1011 1042/2407/1009 +f 1043/2408/1010 1045/2410/1012 1046/2411/1013 +f 1043/2408/1010 1046/2411/1013 1044/2409/1011 +f 1045/2410/1012 1047/2412/1014 1048/2413/1015 +f 1045/2410/1012 1048/2413/1015 1046/2411/1013 +f 1047/2412/1014 1049/2414/1016 1050/2415/1017 +f 1047/2412/1014 1050/2415/1017 1048/2413/1015 +f 1049/2414/1016 1025/2390/994 1028/2393/997 +f 1049/2414/1016 1028/2393/997 1050/2415/1017 +f 1051/487/3 1052/492/3 1053/488/3 +f 1057/2416/1018 1058/2417/1019 1059/2418/1020 +f 1057/2416/1018 1059/2418/1020 1060/2419/1021 +f 1061/2420/1022 1062/2421/1023 1058/2417/1019 +f 1061/2420/1022 1058/2417/1019 1057/2416/1018 +f 1063/2422/1024 1064/2423/1025 1062/2421/1023 +f 1063/2422/1024 1062/2421/1023 1061/2420/1022 +f 1065/2424/1026 1066/2425/1027 1064/2423/1025 +f 1065/2424/1026 1064/2423/1025 1063/2422/1024 +f 1067/2426/1028 1068/2427/1029 1066/2425/1027 +f 1067/2426/1028 1066/2425/1027 1065/2424/1026 +f 1069/2428/1030 1070/2429/1031 1068/2427/1029 +f 1069/2428/1030 1068/2427/1029 1067/2426/1028 +f 1071/2430/1032 1072/2431/1033 1070/2429/1031 +f 1071/2430/1032 1070/2429/1031 1069/2428/1030 +f 1073/2432/1034 1074/2433/1035 1072/2431/1033 +f 1073/2432/1034 1072/2431/1033 1071/2430/1032 +f 1060/2419/1021 1059/2418/1020 1074/2433/1035 +f 1060/2419/1021 1074/2433/1035 1073/2432/1034 +f 4575/2434/1036 1058/2417/1019 1075/2435/1037 +f 4575/2434/1036 1075/2435/1037 1076/2436/1038 +f 1058/2417/1019 1062/2421/1023 1077/2437/1039 +f 1058/2417/1019 1077/2437/1039 1075/2435/1037 +f 1062/2421/1023 1064/2423/1025 104/1576/291 +f 1062/2421/1023 104/1576/291 1077/2437/1039 +f 1064/2423/1025 1066/2425/1027 105/1577/292 +f 1064/2423/1025 105/1577/292 104/1576/291 +f 1066/2425/1027 1068/2427/1029 106/1578/293 +f 1066/2425/1027 106/1578/293 105/1577/292 +f 1068/2427/1029 1070/2429/1031 1078/2438/1040 +f 1068/2427/1029 1078/2438/1040 106/1578/293 +f 1070/2429/1031 1072/2431/1033 1079/2439/1041 +f 1070/2429/1031 1079/2439/1041 1078/2438/1040 +f 1072/2431/1033 4572/2440/1042 1080/2441/1043 +f 1072/2431/1033 1080/2441/1043 1079/2439/1041 +f 4572/2440/1042 4575/2434/1036 1076/2436/1038 +f 4572/2440/1042 1076/2436/1038 1080/2441/1043 +f 1078/2438/1040 1079/2439/1041 1075/2435/1037 +f 1078/2438/1040 1075/2435/1037 1077/2437/1039 +f 1079/2439/1041 1080/2441/1043 1076/2436/1038 +f 1079/2439/1041 1076/2436/1038 1075/2435/1037 +f 1077/2437/1039 104/1576/291 106/1578/293 +f 1077/2437/1039 106/1578/293 1078/2438/1040 +f 1081/2442/1044 1082/2443/1045 1083/2444/1046 +f 1081/2442/1044 1083/2444/1046 1084/2445/1047 +f 1082/2443/1045 1085/2446/1048 1086/2447/1049 +f 1082/2443/1045 1086/2447/1049 1083/2444/1046 +f 1085/2446/1048 1087/2448/1050 1088/2449/1051 +f 1085/2446/1048 1088/2449/1051 1086/2447/1049 +f 4571/2450/1052 1089/2451/1053 1090/2452/1054 +f 4571/2450/1052 1090/2452/1054 4567/2453/1055 +f 1089/2451/1053 1091/2454/1056 1092/2455/1057 +f 1089/2451/1053 1092/2455/1057 1090/2452/1054 +f 4541/2456/1058 1093/2457/1059 1094/2458/1060 +f 4541/2456/1058 1094/2458/1060 4542/2459/1061 +f 1093/2457/1059 1095/2460/1062 1096/2461/1063 +f 1093/2457/1059 1096/2461/1063 1094/2458/1060 +f 1095/2460/1062 1097/2462/1064 1098/2463/1065 +f 1095/2460/1062 1098/2463/1065 1096/2461/1063 +f 1097/2462/1064 1099/2464/1066 1100/2465/1067 +f 1097/2462/1064 1100/2465/1067 1098/2463/1065 +f 1099/2464/1066 1101/2466/1068 1102/2467/1069 +f 1099/2464/1066 1102/2467/1069 1100/2465/1067 +f 1101/2466/1068 1103/2468/1070 1104/2469/1071 +f 1101/2466/1068 1104/2469/1071 1102/2467/1069 +f 1103/2468/1070 1105/2470/1072 1106/2471/1073 +f 1103/2468/1070 1106/2471/1073 1104/2469/1071 +f 1105/2470/1072 1081/2442/1044 1084/2445/1047 +f 1105/2470/1072 1084/2445/1047 1106/2471/1073 +f 4607/2472/1074 4601/2473/1075 1107/2474/1076 +f 4607/2472/1074 1107/2474/1076 1108/2475/1077 +f 4601/2473/1075 4587/2476/1078 1109/2477/1079 +f 4601/2473/1075 1109/2477/1079 1107/2474/1076 +f 4587/2476/1078 4568/2478/1080 1110/2479/1081 +f 4587/2476/1078 1110/2479/1081 1109/2477/1079 +f 4568/2478/1080 4552/2480/1082 1111/2481/1083 +f 4568/2478/1080 1111/2481/1083 1110/2479/1081 +f 4552/2480/1082 4543/2482/1084 1112/2483/1085 +f 4552/2480/1082 1112/2483/1085 1111/2481/1083 +f 4543/2482/1084 4527/2484/1086 1113/2485/1087 +f 4543/2482/1084 1113/2485/1087 1112/2483/1085 +f 4527/2484/1086 4516/2486/1088 1114/2487/1089 +f 4527/2484/1086 1114/2487/1089 1113/2485/1087 +f 4516/2486/1088 4531/2488/1090 1115/2489/1091 +f 4516/2486/1088 1115/2489/1091 1114/2487/1089 +f 4531/2488/1090 4539/2490/1092 1116/2491/1093 +f 4531/2488/1090 1116/2491/1093 1115/2489/1091 +f 4539/2490/1092 4555/2492/1094 1117/2493/1095 +f 4539/2490/1092 1117/2493/1095 1116/2491/1093 +f 4555/2492/1094 4584/2494/1096 1118/2495/1097 +f 4555/2492/1094 1118/2495/1097 1117/2493/1095 +f 4584/2494/1096 4599/2496/1098 1119/2497/1099 +f 4584/2494/1096 1119/2497/1099 1118/2495/1097 +f 4599/2496/1098 4607/2472/1074 1108/2475/1077 +f 4599/2496/1098 1108/2475/1077 1119/2497/1099 +f 4600/2498/1100 4594/2499/1101 4593/2500/1102 +f 4600/2498/1100 4593/2500/1102 4598/2501/1103 +f 4594/2499/1101 4586/2502/1104 4585/2503/1105 +f 4594/2499/1101 4585/2503/1105 4593/2500/1102 +f 4586/2502/1104 4565/2504/1106 4569/2505/1107 +f 4586/2502/1104 4569/2505/1107 4585/2503/1105 +f 4566/2506/1108 4558/2507/1109 4556/2508/1110 +f 4566/2506/1108 4556/2508/1110 4570/2509/1111 +f 4558/2507/1109 4548/2510/1112 4546/2511/1112 +f 4558/2507/1109 4546/2511/1112 4556/2508/1110 +f 4549/2512/1113 4532/2513/1114 4530/2514/1115 +f 4549/2512/1113 4530/2514/1115 4547/2515/1116 +f 4532/2513/1114 4529/2516/1117 4528/2517/1118 +f 4532/2513/1114 4528/2517/1118 4530/2514/1115 +f 4529/2516/1117 4537/2518/1119 4536/2519/1120 +f 4529/2516/1117 4536/2519/1120 4528/2517/1118 +f 4537/2518/1119 4545/2520/1121 4544/2521/1122 +f 4537/2518/1119 4544/2521/1122 4536/2519/1120 +f 4545/2520/1121 4559/2522/1123 4557/2523/1124 +f 4545/2520/1121 4557/2523/1124 4544/2521/1122 +f 4559/2522/1123 4579/2524/1125 4578/2525/1126 +f 4559/2522/1123 4578/2525/1126 4557/2523/1124 +f 4579/2524/1125 4592/2526/1127 4591/2527/1128 +f 4579/2524/1125 4591/2527/1128 4578/2525/1126 +f 4592/2526/1127 4600/2498/1100 4598/2501/1103 +f 4592/2526/1127 4598/2501/1103 4591/2527/1128 +f 1120/2528/1129 1121/2529/1130 1122/2530/1131 +f 1120/2528/1129 1122/2530/1131 1123/2531/1129 +f 1121/2529/1130 1124/2532/1132 1125/2533/1133 +f 1121/2529/1130 1125/2533/1133 1122/2530/1131 +f 1124/2532/1132 1126/2534/1134 1127/2535/1135 +f 1124/2532/1132 1127/2535/1135 1125/2533/1133 +f 1126/2534/1134 1128/2536/1136 1129/2537/1137 +f 1126/2534/1134 1129/2537/1137 1127/2535/1135 +f 1130/2538/1138 1131/2539/1139 1132/2540/1140 +f 1130/2538/1138 1132/2540/1140 1133/2541/1137 +f 4619/2542/1141 4620/2543/1141 1138/2544/1142 +f 4619/2542/1141 1138/2544/1142 1139/2545/1143 +f 1140/2546/1144 4635/2547/1145 4634/2548/5 +f 1140/2546/1144 4634/2548/5 1141/2549/5 +f 1139/2545/1143 1138/2544/1142 1142/2550/1146 +f 1139/2545/1143 1142/2550/1146 1143/2551/1147 +f 1144/2552/1148 4631/2553/1149 4635/2547/1145 +f 1144/2552/1148 4635/2547/1145 1140/2546/1144 +f 1143/2551/1147 1142/2550/1146 1145/2554/1150 +f 1143/2551/1147 1145/2554/1150 1146/2555/1151 +f 1147/2556/1152 4623/2557/1153 4631/2553/1149 +f 1147/2556/1152 4631/2553/1149 1144/2552/1148 +f 1146/2555/1151 1145/2554/1150 1148/2558/1154 +f 1146/2555/1151 1148/2558/1154 1149/2559/1155 +f 1150/2560/1156 4608/2561/1157 4623/2557/1153 +f 1150/2560/1156 4623/2557/1153 1147/2556/1152 +f 1151/2562/1155 4597/2563/1154 4562/2564/1158 +f 1151/2562/1155 4562/2564/1158 1152/2565/1159 +f 1153/2566/1160 4573/2567/1161 4609/2568/1157 +f 1153/2566/1160 4609/2568/1157 1154/2569/1156 +f 1155/2570/1160 1156/2571/1162 1157/2572/1163 +f 1155/2570/1160 1157/2572/1163 4574/2573/1164 +f 1158/2574/1139 4306/2575/1 1159/2576/1 +f 1158/2574/1139 1159/2576/1 1160/2577/1140 +f 1161/2578/1165 1162/2579/1166 1163/2580/1167 +f 1161/2578/1165 1163/2580/1167 1164/2581/1168 +f 1165/2582/1129 1166/2583/1129 1167/2584/1169 +f 1165/2582/1129 1167/2584/1169 1168/2585/1170 +f 1168/2585/1170 1167/2584/1169 1169/2586/1171 +f 1168/2585/1170 1169/2586/1171 1170/2587/1172 +f 1170/2587/1172 1169/2586/1171 1171/2588/1173 +f 1170/2587/1172 1171/2588/1173 1172/2589/1174 +f 1172/2589/1174 1171/2588/1173 1173/2590/1175 +f 1172/2589/1174 1173/2590/1175 1174/2591/1176 +f 1175/2592/1176 1176/2593/1175 1177/2594/1177 +f 1175/2592/1176 1177/2594/1177 1178/2595/1178 +f 3673/2596/1179 1183/2597/1180 1184/2598/1181 +f 3673/2596/1179 1184/2598/1181 3674/2599/1179 +f 1185/2600/1182 1186/2601/6 3612/2602/6 +f 1185/2600/1182 3612/2602/6 3613/2603/1183 +f 1183/2597/1180 1187/2604/1184 1188/2605/1185 +f 1183/2597/1180 1188/2605/1185 1184/2598/1181 +f 1189/2606/1186 1185/2600/1182 3613/2603/1183 +f 1189/2606/1186 3613/2603/1183 3618/2607/1187 +f 1187/2604/1184 1190/2608/1188 1191/2609/1189 +f 1187/2604/1184 1191/2609/1189 1188/2605/1185 +f 1192/2610/1190 1189/2606/1186 3618/2607/1187 +f 1192/2610/1190 3618/2607/1187 3651/2611/1191 +f 1190/2608/1188 1193/2612/1192 1194/2613/1193 +f 1190/2608/1188 1194/2613/1193 1191/2609/1189 +f 1195/2614/1194 1192/2610/1190 3651/2611/1191 +f 1195/2614/1194 3651/2611/1191 3739/2615/1195 +f 1196/2616/1196 1197/2617/1197 3888/2618/1198 +f 1196/2616/1196 3888/2618/1198 3784/2619/1193 +f 1198/2620/1199 1199/2621/1194 3740/2622/1195 +f 1198/2620/1199 3740/2622/1195 3862/2623/1200 +f 1200/2624/1201 3863/2625/1200 1157/2572/1163 +f 1200/2624/1201 1157/2572/1163 1156/2571/1162 +f 1201/2626/1202 1202/2627/1177 1203/2628/1 +f 1201/2626/1202 1203/2628/1 4306/2575/1 +f 1204/2629/1198 1205/2630/1203 1163/2580/1167 +f 1204/2629/1198 1163/2580/1167 1162/2579/1166 +f 1206/2631/1204 1207/2632/1204 1208/2633/1205 +f 1206/2631/1204 1208/2633/1205 1209/2634/1206 +f 1210/2635/1207 1211/2636/1207 1212/2637/1208 +f 1210/2635/1207 1212/2637/1208 1213/2638/1209 +f 1214/2639/1210 1215/2640/1210 1216/2641/1211 +f 1214/2639/1210 1216/2641/1211 1217/2642/1212 +f 1218/2643/1213 1219/2644/1213 1220/2645/1214 +f 1218/2643/1213 1220/2645/1214 1221/2646/1215 +f 1222/2647/1216 1223/2648/1216 1224/2649/1217 +f 1222/2647/1216 1224/2649/1217 1225/2650/1218 +f 1226/2651/1219 1227/2652/1219 1228/2653/1220 +f 1226/2651/1219 1228/2653/1220 1229/2654/1221 +f 1221/2646/1215 1220/2645/1214 1236/2655/1222 +f 1221/2646/1215 1236/2655/1222 1237/2656/1222 +f 1217/2642/1212 1216/2641/1211 1238/2657/1223 +f 1217/2642/1212 1238/2657/1223 1239/2658/1223 +f 1213/2638/1209 1212/2637/1208 1240/2659/1224 +f 1213/2638/1209 1240/2659/1224 1241/2660/1224 +f 1209/2634/1206 1208/2633/1205 1242/2661/1225 +f 1209/2634/1206 1242/2661/1225 1243/2662/1225 +f 1229/2654/1221 1228/2653/1220 1244/2663/1226 +f 1229/2654/1221 1244/2663/1226 1245/2664/1226 +f 1225/2650/1218 1224/2649/1217 1246/2665/1227 +f 1225/2650/1218 1246/2665/1227 1247/2666/1227 +f 1248/2667/1228 1249/2668/1228 1250/2669/1229 +f 1248/2667/1228 1250/2669/1229 1251/2670/1230 +f 1252/2671/1231 1253/2672/1231 1254/2673/1232 +f 1252/2671/1231 1254/2673/1232 4522/2674/1233 +f 1255/2675/1234 1256/2676/1234 1257/2677/1235 +f 1255/2675/1234 1257/2677/1235 1258/2678/1236 +f 1259/2679/1237 1260/2680/1237 1261/2681/1238 +f 1259/2679/1237 1261/2681/1238 1262/2682/1239 +f 1263/2683/1240 1264/2684/1240 1265/2685/1241 +f 1263/2683/1240 1265/2685/1241 1266/2686/1242 +f 1267/2687/1243 1268/2688/1243 4540/2689/1244 +f 1267/2687/1243 4540/2689/1244 1269/2690/1245 +f 1262/2682/1239 1261/2681/1238 1276/2691/1246 +f 1262/2682/1239 1276/2691/1246 1277/2692/1246 +f 1258/2678/1236 1257/2677/1235 1278/2693/1247 +f 1258/2678/1236 1278/2693/1247 1279/2694/1247 +f 4522/2674/1233 1254/2673/1232 1280/2695/1248 +f 4522/2674/1233 1280/2695/1248 1281/2696/1248 +f 1251/2670/1230 1250/2669/1229 1282/2697/1249 +f 1251/2670/1230 1282/2697/1249 1283/2698/1249 +f 1269/2690/1245 4540/2689/1244 1284/2699/1250 +f 1269/2690/1245 1284/2699/1250 1285/2700/1250 +f 1266/2686/1242 1265/2685/1241 1286/2701/1251 +f 1266/2686/1242 1286/2701/1251 1287/2702/1251 +f 1288/2703/1252 1289/2704/1253 1290/2705/1254 +f 1288/2703/1252 1290/2705/1254 1291/2706/1255 +f 1292/2707/1256 1293/2708/1257 1288/2703/1252 +f 1292/2707/1256 1288/2703/1252 1291/2706/1255 +f 1294/2709/1258 1295/2710/1259 1296/2711/1260 +f 1294/2709/1258 1296/2711/1260 1297/2712/1261 +f 1298/2713/1262 1294/2709/1258 1297/2712/1261 +f 1298/2713/1262 1297/2712/1261 1299/2714/1263 +f 1300/2715/1264 1301/2716/1265 1302/2717/1266 +f 1300/2715/1264 1302/2717/1266 1303/2718/1267 +f 1304/2719/1268 1300/2715/1264 1303/2718/1267 +f 1304/2719/1268 1303/2718/1267 1305/2720/1269 +f 4044/2721/1270 4025/2722/1271 1306/2723/1272 +f 4044/2721/1270 1306/2723/1272 1307/2724/1273 +f 1308/2725/1274 4039/2726/1275 4044/2721/1270 +f 1308/2725/1274 4044/2721/1270 1307/2724/1273 +f 3915/2727/1276 3883/2728/1277 1309/2729/1278 +f 3915/2727/1276 1309/2729/1278 1310/2730/1279 +f 1311/2731/1280 3937/2732/1281 3915/2727/1276 +f 1311/2731/1280 3915/2727/1276 1310/2730/1279 +f 4000/2733/1282 3968/2734/1283 1312/2735/1284 +f 4000/2733/1282 1312/2735/1284 1313/2736/1285 +f 4028/2737/1286 4000/2733/1282 1313/2736/1285 +f 4028/2737/1286 1313/2736/1285 1314/2738/1287 +f 4050/2739/1288 4035/2740/1289 1315/2741/1290 +f 4050/2739/1288 1315/2741/1290 1316/2742/1291 +f 4046/2743/1292 4050/2739/1288 1316/2742/1291 +f 4046/2743/1292 1316/2742/1291 1317/2744/1293 +f 3908/2745/1294 3864/2746/1295 1318/2747/1296 +f 3908/2745/1294 1318/2747/1296 1319/2748/1297 +f 3928/2749/1298 3908/2745/1294 1319/2748/1297 +f 3928/2749/1298 1319/2748/1297 1320/2750/1299 +f 1313/2736/1285 3970/2751/1300 1321/2752/1301 +f 1313/2736/1285 1321/2752/1301 1322/2753/1302 +f 1314/2738/1287 1313/2736/1285 1322/2753/1302 +f 1314/2738/1287 1322/2753/1302 1323/2754/1303 +f 1324/2755/1304 4047/2756/1305 4048/2757/1306 +f 1324/2755/1304 4048/2757/1306 1325/2758/1307 +f 4048/2757/1306 4034/2759/1308 1326/2760/1309 +f 4048/2757/1306 1326/2760/1309 1325/2758/1307 +f 1319/2748/1297 1318/2747/1296 1327/2761/1310 +f 1319/2748/1297 1327/2761/1310 1328/2762/1311 +f 1329/2763/1312 3923/2764/1313 1319/2748/1297 +f 1329/2763/1312 1319/2748/1297 1328/2762/1311 +f 3999/2765/1314 3964/2766/1315 1330/2767/1316 +f 3999/2765/1314 1330/2767/1316 1331/2768/1317 +f 4037/2769/1318 3999/2765/1314 1331/2768/1317 +f 4037/2769/1318 1331/2768/1317 1332/2770/1319 +f 4036/2771/1320 4017/2772/1321 1333/2773/1322 +f 4036/2771/1320 1333/2773/1322 1334/2774/1323 +f 1335/2775/1324 4030/2776/1325 4036/2771/1320 +f 1335/2775/1324 4036/2771/1320 1334/2774/1323 +f 3907/2777/1326 3873/2778/1327 1336/2779/1328 +f 3907/2777/1326 1336/2779/1328 1337/2780/1329 +f 1338/2781/1330 3927/2782/1331 3907/2777/1326 +f 1338/2781/1330 3907/2777/1326 1337/2780/1329 +f 3993/2783/1332 3958/2784/1333 1339/2785/1334 +f 3993/2783/1332 1339/2785/1334 1340/2786/1335 +f 4012/2787/1336 3993/2783/1332 1340/2786/1335 +f 4012/2787/1336 1340/2786/1335 1341/2788/1337 +f 1344/2789/1338 4018/2790/1338 4027/2791/1338 +f 1344/2789/1338 4027/2791/1338 4033/2792/1338 +f 1345/2793/1339 3910/2794/1340 3892/2795/1341 +f 1345/2793/1339 3892/2795/1341 1346/2796/1341 +f 1347/2797/1342 3934/2798/1342 3910/2794/1340 +f 1347/2797/1342 3910/2794/1340 1345/2793/1339 +f 1348/2799/1343 3985/2800/1344 3960/2801/1345 +f 1348/2799/1343 3960/2801/1345 1349/2802/1346 +f 1350/2803/1347 4001/2804/1348 3985/2800/1344 +f 1350/2803/1347 3985/2800/1344 1348/2799/1343 +f 1301/2716/1265 1351/2805/1349 1352/2806/1350 +f 1301/2716/1265 1352/2806/1350 1302/2717/1266 +f 1349/2802/1346 3960/2801/1345 1353/2807/1351 +f 1349/2802/1346 1353/2807/1351 1354/2808/1352 +f 3958/2784/1333 1355/2809/1353 3935/2810/1354 +f 3958/2784/1333 3935/2810/1354 1339/2785/1334 +f 3964/2766/1315 1356/2811/1355 3924/2812/1356 +f 3964/2766/1315 3924/2812/1356 1330/2767/1316 +f 3970/2751/1300 1357/2813/1357 3921/2814/1358 +f 3970/2751/1300 3921/2814/1358 1321/2752/1301 +f 3968/2734/1283 3942/2815/1359 3930/2816/1360 +f 3968/2734/1283 3930/2816/1360 1312/2735/1284 +f 4007/2817/1361 1358/2818/1362 1359/2819/1363 +f 4007/2817/1361 1359/2819/1363 4002/2820/1364 +f 4006/2821/1365 4029/2822/1366 1360/2823/1367 +f 4006/2821/1365 1360/2823/1367 1361/2824/1362 +f 4029/2822/1366 4043/2825/1368 1362/2826/1369 +f 4029/2822/1366 1362/2826/1369 1360/2823/1367 +f 1363/2827/1370 1362/2826/1369 4043/2825/1368 +f 1363/2827/1370 4043/2825/1368 4038/2828/1371 +f 4038/2828/1371 4013/2829/1372 1364/2830/1373 +f 4038/2828/1371 1364/2830/1373 1363/2827/1370 +f 4013/2829/1372 4002/2820/1364 1359/2819/1363 +f 4013/2829/1372 1359/2819/1363 1364/2830/1373 +f 3944/2831/1374 1365/2832/1375 1366/2833/1376 +f 3944/2831/1374 1366/2833/1376 3943/2834/1377 +f 3945/2835/1378 3936/2836/1379 1367/2837/1380 +f 3945/2835/1378 1367/2837/1380 1368/2838/1375 +f 3936/2836/1379 3925/2839/1381 1369/2840/1382 +f 3936/2836/1379 1369/2840/1382 1367/2837/1380 +f 3925/2839/1381 3922/2841/1383 1370/2842/1384 +f 3925/2839/1381 1370/2842/1384 1369/2840/1382 +f 1371/2843/1385 1370/2842/1384 3922/2841/1383 +f 1371/2843/1385 3922/2841/1383 3931/2844/1386 +f 3931/2844/1386 3943/2834/1377 1366/2833/1376 +f 3931/2844/1386 1366/2833/1376 1371/2843/1385 +f 1372/2845/1387 1373/2846/1388 1374/2847/1389 +f 1372/2845/1387 1374/2847/1389 1375/2848/1390 +f 1376/2849/1388 1377/2850/1391 1378/2851/1392 +f 1376/2849/1388 1378/2851/1392 1379/2852/1393 +f 1377/2850/1391 1380/2853/1394 1381/2854/1395 +f 1377/2850/1391 1381/2854/1395 1378/2851/1392 +f 1380/2853/1394 1382/2855/1396 1383/2856/1397 +f 1380/2853/1394 1383/2856/1397 1381/2854/1395 +f 1382/2855/1396 1384/2857/1398 1385/2858/1399 +f 1382/2855/1396 1385/2858/1399 1383/2856/1397 +f 1384/2857/1398 1386/2859/1400 1387/2860/1401 +f 1384/2857/1398 1387/2860/1401 1385/2858/1399 +f 1386/2859/1400 1388/2861/1402 1389/2862/1403 +f 1386/2859/1400 1389/2862/1403 1387/2860/1401 +f 1388/2861/1402 1390/2863/1404 1391/2864/1405 +f 1388/2861/1402 1391/2864/1405 1389/2862/1403 +f 1390/2863/1404 1392/2865/1406 1393/2866/1407 +f 1390/2863/1404 1393/2866/1407 1391/2864/1405 +f 1392/2865/1406 1394/2867/1408 1395/2868/1409 +f 1392/2865/1406 1395/2868/1409 1393/2866/1407 +f 1394/2867/1408 1396/2869/1410 1397/2870/1411 +f 1394/2867/1408 1397/2870/1411 1395/2868/1409 +f 1396/2869/1410 1372/2845/1387 1375/2848/1390 +f 1396/2869/1410 1375/2848/1390 1397/2870/1411 +f 1398/2871/1412 1399/2872/1413 1400/2873/1414 +f 1398/2871/1412 1400/2873/1414 1401/2874/1415 +f 1402/2875/1416 1398/2871/1412 1401/2874/1415 +f 1402/2875/1416 1401/2874/1415 1403/2876/1417 +f 1404/2877/1418 1402/2875/1416 1403/2876/1417 +f 1404/2877/1418 1403/2876/1417 1405/2878/1419 +f 1406/2879/1420 1404/2877/1418 1405/2878/1419 +f 1406/2879/1420 1405/2878/1419 1407/2880/1421 +f 1408/2881/1422 1406/2879/1420 1407/2880/1421 +f 1408/2881/1422 1407/2880/1421 1409/2882/1423 +f 1410/2883/1424 1408/2881/1422 1409/2882/1423 +f 1410/2883/1424 1409/2882/1423 1411/2884/1425 +f 1412/2885/1426 1410/2883/1424 1411/2884/1425 +f 1412/2885/1426 1411/2884/1425 1413/2886/1427 +f 1414/2887/1428 1412/2885/1426 1413/2886/1427 +f 1414/2887/1428 1413/2886/1427 1415/2888/1429 +f 1416/2889/1430 1417/2890/1428 1418/2891/1429 +f 1416/2889/1430 1418/2891/1429 1419/2892/1431 +f 1420/2893/1432 1416/2889/1430 1419/2892/1431 +f 1420/2893/1432 1419/2892/1431 1421/2894/1433 +f 1422/2895/1434 1420/2893/1432 1421/2894/1433 +f 1422/2895/1434 1421/2894/1433 1423/2896/1435 +f 1399/2872/1413 1422/2895/1434 1423/2896/1435 +f 1399/2872/1413 1423/2896/1435 1400/2873/1414 +f 1424/2897/1436 1425/2898/1437 1426/2899/1438 +f 1424/2897/1436 1426/2899/1438 1427/2900/1439 +f 1428/2901/1440 1424/2897/1436 1427/2900/1439 +f 1428/2901/1440 1427/2900/1439 1429/2902/1441 +f 1430/2903/1442 1428/2901/1440 1429/2902/1441 +f 1430/2903/1442 1429/2902/1441 1431/2904/1443 +f 1432/2905/1444 1430/2903/1442 1431/2904/1443 +f 1432/2905/1444 1431/2904/1443 1433/2906/1445 +f 1434/2907/1446 1432/2905/1444 1433/2906/1445 +f 1434/2907/1446 1433/2906/1445 1435/2908/1447 +f 1436/2909/1448 1434/2907/1446 1435/2908/1447 +f 1436/2909/1448 1435/2908/1447 1437/2910/1449 +f 1438/2911/1450 1436/2909/1448 1437/2910/1449 +f 1438/2911/1450 1437/2910/1449 1439/2912/1451 +f 1440/2913/1452 1438/2911/1450 1439/2912/1451 +f 1440/2913/1452 1439/2912/1451 1441/2914/1453 +f 1442/2915/1454 1443/2916/1455 1444/2917/1453 +f 1442/2915/1454 1444/2917/1453 1445/2918/1456 +f 1446/2919/1457 1442/2915/1454 1445/2918/1456 +f 1446/2919/1457 1445/2918/1456 1447/2920/1458 +f 1448/2921/1459 1446/2919/1457 1447/2920/1458 +f 1448/2921/1459 1447/2920/1458 1449/2922/1460 +f 1425/2898/1437 1448/2921/1459 1449/2922/1460 +f 1425/2898/1437 1449/2922/1460 1426/2899/1438 +f 1450/2923/1461 1451/2924/1462 3886/2925/1463 +f 1450/2923/1461 3886/2925/1463 3865/2926/1464 +f 1452/2927/1465 1453/2928/1466 3866/2929/1464 +f 1452/2927/1465 3866/2929/1464 3851/2930/1467 +f 1454/2931/1468 1452/2927/1465 3851/2930/1467 +f 1454/2931/1468 3851/2930/1467 3841/2932/1469 +f 1455/2933/1470 1454/2931/1468 3841/2932/1469 +f 1455/2933/1470 3841/2932/1469 3836/2934/1471 +f 1456/2935/1472 1455/2933/1470 3836/2934/1471 +f 1456/2935/1472 3836/2934/1471 3844/2936/1473 +f 1457/2937/1474 1456/2935/1472 3844/2936/1473 +f 1457/2937/1474 3844/2936/1473 3856/2938/1475 +f 1458/2939/1476 1457/2937/1474 3856/2938/1475 +f 1458/2939/1476 3856/2938/1475 3876/2940/1477 +f 1459/2941/1478 1458/2939/1476 3876/2940/1477 +f 1459/2941/1478 3876/2940/1477 3893/2942/1479 +f 1460/2943/1480 1459/2941/1478 3893/2942/1479 +f 1460/2943/1480 3893/2942/1479 3909/2944/1481 +f 1461/2945/1482 1460/2943/1480 3909/2944/1481 +f 1461/2945/1482 3909/2944/1481 3911/2946/1483 +f 1462/2947/1484 1461/2945/1482 3911/2946/1483 +f 1462/2947/1484 3911/2946/1483 3902/2948/1485 +f 1451/2924/1462 1462/2947/1484 3902/2948/1485 +f 1451/2924/1462 3902/2948/1485 3886/2925/1463 +f 1463/2949/1486 1464/2950/1487 4019/2951/1488 +f 1463/2949/1486 4019/2951/1488 4010/2952/1489 +f 1465/2953/1490 1463/2949/1486 4010/2952/1489 +f 1465/2953/1490 4010/2952/1489 3997/2954/1491 +f 1466/2955/1492 1465/2953/1490 3997/2954/1491 +f 1466/2955/1492 3997/2954/1491 3982/2956/1493 +f 1467/2957/1494 1466/2955/1492 3982/2956/1493 +f 1467/2957/1494 3982/2956/1493 3971/2958/1495 +f 1468/2959/1496 1467/2957/1494 3971/2958/1495 +f 1468/2959/1496 3971/2958/1495 3962/2960/1497 +f 1469/2961/1498 1468/2959/1496 3962/2960/1497 +f 1469/2961/1498 3962/2960/1497 3967/2962/1499 +f 1470/2963/1500 1469/2961/1498 3967/2962/1499 +f 1470/2963/1500 3967/2962/1499 3978/2964/1501 +f 1471/2965/1502 1470/2963/1500 3978/2964/1501 +f 1471/2965/1502 3978/2964/1501 3990/2966/1503 +f 1472/2967/1504 1473/2968/1502 3991/2969/1503 +f 1472/2967/1504 3991/2969/1503 4003/2970/1505 +f 1474/2971/1506 1472/2967/1504 4003/2970/1505 +f 1474/2971/1506 4003/2970/1505 4015/2972/1507 +f 1475/2973/1508 1474/2971/1506 4015/2972/1507 +f 1475/2973/1508 4015/2972/1507 4021/2974/1509 +f 1464/2950/1487 1475/2973/1508 4021/2974/1509 +f 1464/2950/1487 4021/2974/1509 4019/2951/1488 +f 4045/2975/1510 4032/2976/1511 1476/2977/1512 +f 4045/2975/1510 1476/2977/1512 1477/2978/1513 +f 4031/2979/1511 4014/2980/1514 1478/2981/1515 +f 4031/2979/1511 1478/2981/1515 1479/2982/1512 +f 4014/2980/1514 4005/2983/1516 1480/2984/1517 +f 4014/2980/1514 1480/2984/1517 1478/2981/1515 +f 4005/2983/1516 4004/2985/1518 1481/2986/1519 +f 4005/2983/1516 1481/2986/1519 1480/2984/1517 +f 4004/2985/1518 4008/2987/1520 1482/2988/1521 +f 4004/2985/1518 1482/2988/1521 1481/2986/1519 +f 4008/2987/1520 4020/2989/1522 1483/2990/1523 +f 4008/2987/1520 1483/2990/1523 1482/2988/1521 +f 4020/2989/1522 4040/2991/1524 1484/2992/1525 +f 4020/2989/1522 1484/2992/1525 1483/2990/1523 +f 4040/2991/1524 4051/2993/1526 1485/2994/1527 +f 4040/2991/1524 1485/2994/1527 1484/2992/1525 +f 4051/2993/1526 4053/2995/1528 1486/2996/1529 +f 4051/2993/1526 1486/2996/1529 1485/2994/1527 +f 4053/2995/1528 4054/2997/1530 1487/2998/1531 +f 4053/2995/1528 1487/2998/1531 1486/2996/1529 +f 4054/2997/1530 4052/2999/1532 1488/3000/1533 +f 4054/2997/1530 1488/3000/1533 1487/2998/1531 +f 4052/2999/1532 4045/2975/1510 1477/2978/1513 +f 4052/2999/1532 1477/2978/1513 1488/3000/1533 +f 1492/520/82 1496/524/82 1489/517/82 +f 1497/525/82 1498/3001/82 1496/524/82 +f 1501/528/84 1503/530/84 1504/3002/84 +f 4267/3003/1534 1509/3004/1534 1510/3005/1534 +f 4267/3003/1534 1510/3005/1534 4229/3006/1534 +f 4595/557/91 4629/3007/91 1525/558/91 +f 4560/3008/90 1541/3009/90 1542/3010/90 +f 4560/3008/90 1542/3010/90 4561/3011/90 +f 1555/3012/92 1556/3013/92 4641/3014/92 +f 1555/3012/92 4641/3014/92 4663/3015/92 +f 4658/624/100 1575/626/100 1576/3016/100 +f 1581/635/94 1583/637/94 1584/3017/94 +f 1593/3018/90 1594/3019/90 4627/3020/90 +f 1593/3018/90 4627/3020/90 4554/3021/90 +f 1597/3022/1535 1598/3023/1536 3614/3024/1536 +f 1597/3022/1535 3614/3024/1536 3598/3025/1537 +f 3987/3026/1538 3983/3027/1539 1599/3028/1539 +f 3987/3026/1538 1599/3028/1539 1600/3029/1540 +f 1601/3030/1541 1602/3031/1542 3650/3032/1543 +f 1601/3030/1541 3650/3032/1543 3615/3033/1541 +f 1603/3034/1544 1604/3035/1545 3604/3036/1545 +f 1603/3034/1544 3604/3036/1545 3635/3037/1546 +f 1605/3038/1547 1606/3039/1548 3938/3040/1549 +f 1605/3038/1547 3938/3040/1549 3974/3041/1547 +f 3950/3042/1550 1607/3043/1551 1608/3044/1552 +f 3950/3042/1550 1608/3044/1552 3984/3045/1552 +f 1609/3046/1553 1597/3022/1535 3598/3025/1537 +f 1609/3046/1553 3598/3025/1537 3605/3047/1553 +f 3975/3048/1554 3987/3026/1538 1600/3029/1540 +f 3975/3048/1554 1600/3029/1540 1610/3049/1554 +f 138/1610/325 136/1608/323 1611/3050/1555 +f 138/1610/325 1611/3050/1555 1612/3051/2 +f 1613/3052/1556 1603/3034/1544 3635/3037/1546 +f 1613/3052/1556 3635/3037/1546 3766/3053/1557 +f 3650/3032/1543 1602/3031/1542 1614/3054/1558 +f 3650/3032/1543 1614/3054/1558 3776/3055/1559 +f 139/1611/326 1612/3051/2 1611/3050/1555 +f 139/1611/326 1611/3050/1555 140/1612/327 +f 3776/3055/1559 1614/3054/1558 1607/3043/1551 +f 3776/3055/1559 1607/3043/1551 3950/3042/1550 +f 3938/3040/1549 1606/3039/1548 1613/3052/1556 +f 3938/3040/1549 1613/3052/1556 3766/3053/1557 +f 1615/3056/1560 1616/3057/1561 3606/3058/1562 +f 1615/3056/1560 3606/3058/1562 3610/3059/1560 +f 3965/3060/1563 3986/3061/1564 1617/3062/1565 +f 3965/3060/1563 1617/3062/1565 1618/3063/1563 +f 1619/3064/1566 1620/3065/1567 3639/3066/1568 +f 1619/3064/1566 3639/3066/1568 3616/3067/1566 +f 1621/3068/1569 1622/3069/1545 3611/3070/1545 +f 1621/3068/1569 3611/3070/1545 3630/3071/1570 +f 3986/3061/1564 3988/3072/1571 1623/3073/1571 +f 3986/3061/1564 1623/3073/1571 1617/3062/1565 +f 3606/3058/1562 1616/3057/1561 1624/3074/1572 +f 3606/3058/1562 1624/3074/1572 3617/3075/1572 +f 1625/3076/1573 1626/3077/1574 147/1619/334 +f 1625/3076/1573 147/1619/334 148/1620/335 +f 1627/3078/1575 1621/3068/1569 3630/3071/1570 +f 1627/3078/1575 3630/3071/1570 3743/3079/1576 +f 3639/3066/1568 1620/3065/1567 1628/3080/1577 +f 3639/3066/1568 1628/3080/1577 3756/3081/1578 +f 1629/3082/1579 1630/3083/1580 3901/3084/1581 +f 1629/3082/1579 3901/3084/1581 3966/3085/1579 +f 3933/3086/1582 1631/3087/1583 1632/3088/1584 +f 3933/3086/1582 1632/3088/1584 3989/3089/1584 +f 1630/3083/1580 1627/3078/1575 3743/3079/1576 +f 1630/3083/1580 3743/3079/1576 3901/3084/1581 +f 3756/3081/1578 1628/3080/1577 1631/3087/1583 +f 3756/3081/1578 1631/3087/1583 3933/3086/1582 +f 153/1625/340 150/1622/337 1626/3077/1574 +f 153/1625/340 1626/3077/1574 1625/3076/1573 +f 3948/3090/1585 3955/3091/1586 1635/3092/1586 +f 3948/3090/1585 1635/3092/1586 1636/3093/1587 +f 1637/3094/1588 1638/3095/1589 3619/3096/1590 +f 1637/3094/1588 3619/3096/1590 3608/3097/1588 +f 1639/3098/1591 1640/3099/1592 3609/3100/1593 +f 1639/3098/1591 3609/3100/1593 3622/3101/1594 +f 1641/3102/1595 1642/3103/1596 3884/3104/1597 +f 1641/3102/1595 3884/3104/1597 3917/3105/1595 +f 3919/3106/1598 1643/3107/1599 1644/3108/1600 +f 3919/3106/1598 1644/3108/1600 3956/3109/1600 +f 1645/3110/1592 3595/3111/1601 3597/3112/1601 +f 1645/3110/1592 3597/3112/1601 3609/3100/1593 +f 3918/3113/1602 3948/3090/1585 1636/3093/1587 +f 3918/3113/1602 1636/3093/1587 1646/3114/1602 +f 1647/3115/1603 1639/3098/1591 3622/3101/1594 +f 1647/3115/1603 3622/3101/1594 1648/3116/1604 +f 3619/3096/1590 1638/3095/1589 1649/3117/1605 +f 3619/3096/1590 1649/3117/1605 1650/3118/1606 +f 158/1630/345 156/1628/343 3751/3119/1607 +f 158/1630/345 3751/3119/1607 3757/3120/2 +f 159/1631/346 3757/3120/2 3751/3119/1607 +f 159/1631/346 3751/3119/1607 160/1632/347 +f 1650/3118/1606 1649/3117/1605 1643/3107/1599 +f 1650/3118/1606 1643/3107/1599 3919/3106/1598 +f 3884/3104/1597 1642/3103/1596 1647/3115/1603 +f 3884/3104/1597 1647/3115/1603 1648/3116/1604 +f 166/1638/353 165/1637/352 1651/3121/1608 +f 166/1638/353 1651/3121/1608 1652/3122/1609 +f 165/1637/352 168/1640/355 1653/3123/1610 +f 165/1637/352 1653/3123/1610 1651/3121/1608 +f 168/1640/355 169/1641/356 1654/3124/1611 +f 168/1640/355 1654/3124/1611 1653/3123/1610 +f 169/1641/356 170/1642/357 1655/3125/1612 +f 169/1641/356 1655/3125/1612 1654/3124/1611 +f 170/1642/357 171/1643/358 1656/3126/1613 +f 170/1642/357 1656/3126/1613 1655/3125/1612 +f 171/1643/358 166/1638/353 1652/3122/1609 +f 171/1643/358 1652/3122/1609 3828/3127/1614 +f 1652/3122/1609 1651/3121/1608 1657/3128/1615 +f 1652/3122/1609 1657/3128/1615 1658/3129/1616 +f 1651/3121/1608 1653/3123/1610 1659/3130/1617 +f 1651/3121/1608 1659/3130/1617 3835/3131/1618 +f 1653/3123/1610 1654/3124/1611 1660/3132/1619 +f 1653/3123/1610 1660/3132/1619 3822/3133/1620 +f 1654/3124/1611 1655/3125/1612 1661/3134/1621 +f 1654/3124/1611 1661/3134/1621 3809/3135/1622 +f 1655/3125/1612 1656/3126/1613 1662/3136/1623 +f 1655/3125/1612 1662/3136/1623 3811/3137/1624 +f 3828/3127/1614 1652/3122/1609 3838/3138/1625 +f 3828/3127/1614 3838/3138/1625 3829/3139/1626 +f 1658/3129/1616 1657/3128/1615 1663/3140/1627 +f 1658/3129/1616 1663/3140/1627 1664/3141/1628 +f 3835/3131/1618 1659/3130/1617 1665/3142/1629 +f 3835/3131/1618 1665/3142/1629 1663/3140/1627 +f 3822/3133/1620 1660/3132/1619 1666/3143/1630 +f 3822/3133/1620 1666/3143/1630 3823/3144/1631 +f 3809/3135/1622 1661/3134/1621 1667/3145/1632 +f 3809/3135/1622 1667/3145/1632 1666/3143/1630 +f 3811/3137/1624 1662/3136/1623 1668/3146/1633 +f 3811/3137/1624 1668/3146/1633 1667/3145/1632 +f 3829/3139/1626 3838/3138/1625 1664/3141/1628 +f 3829/3139/1626 1664/3141/1628 1668/3146/1633 +f 1664/3141/1628 1663/3140/1627 173/1645/360 +f 1664/3141/1628 173/1645/360 172/1644/359 +f 1663/3140/1627 1665/3142/1629 175/1647/362 +f 1663/3140/1627 175/1647/362 173/1645/360 +f 3823/3144/1631 1666/3143/1630 176/1648/363 +f 3823/3144/1631 176/1648/363 175/1647/362 +f 1666/3143/1630 1667/3145/1632 177/1649/364 +f 1666/3143/1630 177/1649/364 176/1648/363 +f 1667/3145/1632 1668/3146/1633 178/1650/365 +f 1667/3145/1632 178/1650/365 177/1649/364 +f 1668/3146/1633 1664/3141/1628 172/1644/359 +f 1668/3146/1633 172/1644/359 178/1650/365 +f 180/1652/367 179/1651/366 1669/3147/1634 +f 180/1652/367 1669/3147/1634 1670/3148/1635 +f 179/1651/366 182/1654/369 1671/3149/1636 +f 179/1651/366 1671/3149/1636 1669/3147/1634 +f 182/1654/369 183/1655/370 1672/3150/1637 +f 182/1654/369 1672/3150/1637 1671/3149/1636 +f 183/1655/370 184/1656/371 1673/3151/1638 +f 183/1655/370 1673/3151/1638 1672/3150/1637 +f 184/1656/371 185/1657/372 1674/3152/1639 +f 184/1656/371 1674/3152/1639 1673/3151/1638 +f 185/1657/372 180/1652/367 1670/3148/1635 +f 185/1657/372 1670/3148/1635 3790/3153/1640 +f 1670/3148/1635 1669/3147/1634 1675/3154/1641 +f 1670/3148/1635 1675/3154/1641 1676/3155/1642 +f 1669/3147/1634 1671/3149/1636 1677/3156/1643 +f 1669/3147/1634 1677/3156/1643 3802/3157/1644 +f 1671/3149/1636 1672/3150/1637 1678/3158/1645 +f 1671/3149/1636 1678/3158/1645 3800/3159/1646 +f 1672/3150/1637 1673/3151/1638 1679/3160/1647 +f 1672/3150/1637 1679/3160/1647 3789/3161/1648 +f 1673/3151/1638 1674/3152/1639 1680/3162/1649 +f 1673/3151/1638 1680/3162/1649 3780/3163/1650 +f 3790/3153/1640 1670/3148/1635 3797/3164/1651 +f 3790/3153/1640 3797/3164/1651 3786/3165/1652 +f 1676/3155/1642 1675/3154/1641 1681/3166/1653 +f 1676/3155/1642 1681/3166/1653 1682/3167/1654 +f 3802/3157/1644 1677/3156/1643 1683/3168/1655 +f 3802/3157/1644 1683/3168/1655 1681/3166/1653 +f 3800/3159/1646 1678/3158/1645 1684/3169/1656 +f 3800/3159/1646 1684/3169/1656 3796/3170/1657 +f 3789/3161/1648 1679/3160/1647 1685/3171/1658 +f 3789/3161/1648 1685/3171/1658 1684/3169/1656 +f 3780/3163/1650 1680/3162/1649 1686/3172/1659 +f 3780/3163/1650 1686/3172/1659 1685/3171/1658 +f 3786/3165/1652 3797/3164/1651 1682/3167/1654 +f 3786/3165/1652 1682/3167/1654 1686/3172/1659 +f 1682/3167/1654 1681/3166/1653 187/1659/374 +f 1682/3167/1654 187/1659/374 186/1658/373 +f 1681/3166/1653 1683/3168/1655 189/1661/376 +f 1681/3166/1653 189/1661/376 187/1659/374 +f 3796/3170/1657 1684/3169/1656 190/1662/377 +f 3796/3170/1657 190/1662/377 189/1661/376 +f 1684/3169/1656 1685/3171/1658 191/1663/378 +f 1684/3169/1656 191/1663/378 190/1662/377 +f 1685/3171/1658 1686/3172/1659 192/1664/379 +f 1685/3171/1658 192/1664/379 191/1663/378 +f 1686/3172/1659 1682/3167/1654 186/1658/373 +f 1686/3172/1659 186/1658/373 192/1664/379 +f 1687/3173/1660 1688/3174/1661 1689/3175/1662 +f 1687/3173/1660 1689/3175/1662 1690/3176/1663 +f 1688/3174/1661 1691/3177/1664 1692/3178/1665 +f 1688/3174/1661 1692/3178/1665 1689/3175/1662 +f 1691/3177/1664 1693/3179/1666 1694/3180/1667 +f 1691/3177/1664 1694/3180/1667 1692/3178/1665 +f 1693/3179/1666 1695/3181/1668 1696/3182/1669 +f 1693/3179/1666 1696/3182/1669 1694/3180/1667 +f 1695/3181/1668 1697/3183/1670 1698/3184/1671 +f 1695/3181/1668 1698/3184/1671 1696/3182/1669 +f 1697/3183/1670 1699/3185/1672 1700/3186/1673 +f 1697/3183/1670 1700/3186/1673 1698/3184/1671 +f 1699/3185/1672 1701/3187/1674 1702/3188/1675 +f 1699/3185/1672 1702/3188/1675 1700/3186/1673 +f 1701/3187/1674 1703/3189/1676 1704/3190/1677 +f 1701/3187/1674 1704/3190/1677 1702/3188/1675 +f 1703/3189/1676 1705/3191/1678 1706/3192/1679 +f 1703/3189/1676 1706/3192/1679 1704/3190/1677 +f 1705/3191/1678 1687/3173/1660 1690/3176/1663 +f 1705/3191/1678 1690/3176/1663 1706/3192/1679 +f 3742/658/103 3758/664/103 3846/659/103 +f 3858/660/103 3848/663/103 3759/661/103 +f 3788/662/103 3848/663/103 3824/3193/103 +f 1707/3194/1680 1708/3195/1681 1709/3196/1682 +f 1707/3194/1680 1709/3196/1682 1710/3197/1683 +f 1708/3195/1681 1711/3198/1684 1712/3199/1685 +f 1708/3195/1681 1712/3199/1685 1709/3196/1682 +f 1711/3198/1684 1713/3200/1686 1714/3201/1687 +f 1711/3198/1684 1714/3201/1687 1712/3199/1685 +f 1713/3200/1686 1715/3202/1688 1716/3203/1689 +f 1713/3200/1686 1716/3203/1689 1714/3201/1687 +f 1715/3202/1688 1717/3204/1690 1718/3205/1691 +f 1715/3202/1688 1718/3205/1691 1716/3203/1689 +f 1717/3204/1690 1719/3206/1692 1720/3207/1693 +f 1717/3204/1690 1720/3207/1693 1718/3205/1691 +f 1721/3208/1694 1722/3209/1695 1723/3210/1696 +f 1721/3208/1694 1723/3210/1696 1724/3211/1693 +f 1722/3209/1695 1707/3194/1680 1710/3197/1683 +f 1722/3209/1695 1710/3197/1683 1723/3210/1696 +f 1743/3212/1697 1744/3213/1698 1745/3214/1699 +f 1744/3213/1698 193/1665/380 195/1667/382 +f 1744/3213/1698 195/1667/382 1745/3214/1699 +f 194/1666/381 196/1668/383 198/1670/385 +f 194/1666/381 198/1670/385 195/1667/382 +f 197/1669/384 1746/3215/1700 1747/3216/1701 +f 197/1669/384 1747/3216/1701 198/1670/385 +f 200/1672/387 1743/3212/1697 1745/3214/1699 +f 200/1672/387 1745/3214/1699 201/1673/388 +f 1718/3205/1691 1720/3207/1693 3700/3217/1702 +f 1718/3205/1691 3700/3217/1702 3687/3218/1703 +f 1716/3203/1689 1718/3205/1691 3687/3218/1703 +f 1716/3203/1689 3687/3218/1703 3689/3219/1704 +f 1714/3201/1687 1716/3203/1689 3689/3219/1704 +f 1714/3201/1687 3689/3219/1704 3709/3220/1705 +f 1712/3199/1685 1714/3201/1687 3709/3220/1705 +f 1712/3199/1685 3709/3220/1705 3727/3221/1706 +f 1709/3196/1682 1712/3199/1685 3727/3221/1706 +f 1709/3196/1682 3727/3221/1706 3732/3222/1707 +f 1710/3197/1683 1709/3196/1682 3732/3222/1707 +f 1710/3197/1683 3732/3222/1707 3731/3223/1708 +f 1723/3210/1696 1710/3197/1683 3731/3223/1708 +f 1723/3210/1696 3731/3223/1708 3724/3224/1709 +f 1724/3211/1693 1723/3210/1696 3724/3224/1709 +f 1724/3211/1693 3724/3224/1709 3701/3225/1710 +f 1748/3226/1711 1749/3227/1712 1750/3228/1713 +f 1748/3226/1711 1750/3228/1713 1751/3229/1711 +f 3833/3230/1714 3840/3231/1715 1752/3232/1716 +f 3833/3230/1714 1752/3232/1716 1753/3233/1717 +f 3889/3234/1718 3882/3235/1719 1754/3236/1720 +f 3889/3234/1718 1754/3236/1720 1755/3237/1718 +f 1756/3238/1721 1757/3239/1722 1758/3240/1721 +f 1756/3238/1721 1758/3240/1721 1759/3241/1721 +f 1762/3242/1723 3951/3243/1724 1763/3244/1724 +f 1762/3242/1723 1763/3244/1724 1764/3245/1725 +f 1765/3246/1726 1766/3247/1727 1767/3248/1728 +f 1765/3246/1726 1767/3248/1728 3874/3249/1728 +f 3914/3250/1729 3857/3251/1730 3875/3252/1731 +f 3914/3250/1729 3875/3252/1731 3952/3253/1732 +f 3882/3235/1719 1768/3254/1733 1769/3255/1734 +f 3882/3235/1719 1769/3255/1734 1754/3236/1720 +f 1750/3228/1713 1749/3227/1712 1770/3256/1735 +f 1750/3228/1713 1770/3256/1735 1771/3257/1736 +f 1752/3232/1716 3840/3231/1715 3849/3258/1737 +f 1752/3232/1716 3849/3258/1737 3895/3259/1738 +f 1768/3254/1733 1762/3242/1723 1764/3245/1725 +f 1768/3254/1733 1764/3245/1725 1769/3255/1734 +f 1771/3257/1736 1770/3256/1735 1766/3247/1727 +f 1771/3257/1736 1766/3247/1727 1765/3246/1726 +f 3895/3259/1738 3849/3258/1737 3857/3251/1730 +f 3895/3259/1738 3857/3251/1730 3914/3250/1729 +f 1812/3260/1739 1813/3261/1740 1814/3262/1741 +f 1812/3260/1739 1814/3262/1741 1815/3263/1742 +f 1813/3261/1740 1816/3264/1743 1817/3265/1744 +f 1813/3261/1740 1817/3265/1744 1814/3262/1741 +f 1816/3264/1743 1818/3266/1745 1819/3267/1746 +f 1816/3264/1743 1819/3267/1746 1817/3265/1744 +f 1818/3266/1745 1820/3268/1747 1821/3269/1748 +f 1818/3266/1745 1821/3269/1748 1819/3267/1746 +f 1820/3268/1747 1822/3270/1749 1823/3271/1750 +f 1820/3268/1747 1823/3271/1750 1821/3269/1748 +f 1822/3270/1749 1824/3272/1751 1825/3273/1752 +f 1822/3270/1749 1825/3273/1752 1823/3271/1750 +f 1824/3272/1751 1826/3274/1753 1827/3275/1754 +f 1824/3272/1751 1827/3275/1754 1825/3273/1752 +f 1826/3274/1753 1828/3276/1755 1829/3277/1756 +f 1826/3274/1753 1829/3277/1756 1827/3275/1754 +f 1828/3276/1755 1830/3278/1757 1831/3279/1758 +f 1828/3276/1755 1831/3279/1758 1829/3277/1756 +f 1830/3278/1757 1812/3260/1739 1815/3263/1742 +f 1830/3278/1757 1815/3263/1742 1831/3279/1758 +f 3998/775/124 4016/3280/124 3981/776/124 +f 3843/782/126 1847/784/126 3859/3281/126 +f 3963/797/130 1857/799/130 3947/3282/130 +f 1868/816/135 1869/3283/135 1870/817/135 +f 3680/835/132 3697/837/132 3686/3284/132 +f 1882/3285/1739 1883/3286/1740 1884/3287/1741 +f 1882/3285/1739 1884/3287/1741 1885/3288/1759 +f 1883/3286/1740 1886/3289/1743 1887/3290/1744 +f 1883/3286/1740 1887/3290/1744 1884/3287/1741 +f 1886/3289/1743 1888/3291/1745 1889/3292/1746 +f 1886/3289/1743 1889/3292/1746 1887/3290/1744 +f 1888/3291/1745 1890/3293/1747 1891/3294/1748 +f 1888/3291/1745 1891/3294/1748 1889/3292/1746 +f 1890/3293/1747 1892/3295/1749 1893/3296/1750 +f 1890/3293/1747 1893/3296/1750 1891/3294/1748 +f 1892/3295/1749 1894/3297/1751 1895/3298/1752 +f 1892/3295/1749 1895/3298/1752 1893/3296/1750 +f 1894/3297/1751 1896/3299/1753 1897/3300/1754 +f 1894/3297/1751 1897/3300/1754 1895/3298/1752 +f 1896/3299/1753 1898/3301/1755 1899/3302/1756 +f 1896/3299/1753 1899/3302/1756 1897/3300/1754 +f 1898/3301/1755 1900/3303/1757 1901/3304/1758 +f 1898/3301/1755 1901/3304/1758 1899/3302/1756 +f 1900/3303/1757 1882/3285/1739 1885/3288/1759 +f 1900/3303/1757 1885/3288/1759 1901/3304/1758 +f 1908/856/127 1909/3305/127 1910/857/127 +f 3725/867/122 1916/3306/122 1917/868/122 +f 1932/3307/1760 1933/3308/1761 1934/3309/1762 +f 1932/3307/1760 1934/3309/1762 1935/3310/1760 +f 3637/3311/1763 3631/3312/1764 1936/3313/1765 +f 3637/3311/1763 1936/3313/1765 1937/3314/1766 +f 3703/3315/1767 3676/3316/1768 1938/3317/1769 +f 3703/3315/1767 1938/3317/1769 1939/3318/1767 +f 1946/3319/1770 3716/3320/1723 1947/3321/1723 +f 1946/3319/1770 1947/3321/1723 1948/3322/1771 +f 1949/3323/1772 1950/3324/1773 1951/3325/1774 +f 1949/3323/1772 1951/3325/1774 3653/3326/1774 +f 3690/3327/1775 3636/3328/1776 3654/3329/1777 +f 3690/3327/1775 3654/3329/1777 3717/3330/1778 +f 3676/3316/1768 1952/3331/1779 1953/3332/1780 +f 3676/3316/1768 1953/3332/1780 1938/3317/1769 +f 1934/3309/1762 1933/3308/1761 1954/3333/1781 +f 1934/3309/1762 1954/3333/1781 1955/3334/1782 +f 1936/3313/1765 3631/3312/1764 3628/3335/1783 +f 1936/3313/1765 3628/3335/1783 3675/3336/1784 +f 1952/3331/1779 1946/3319/1770 1948/3322/1771 +f 1952/3331/1779 1948/3322/1771 1953/3332/1780 +f 1955/3334/1782 1954/3333/1781 1950/3324/1773 +f 1955/3334/1782 1950/3324/1773 1949/3323/1772 +f 3675/3336/1784 3628/3335/1783 3636/3328/1776 +f 3675/3336/1784 3636/3328/1776 3690/3327/1775 +f 1956/3337/1785 1957/3338/1786 1958/3339/1787 +f 1956/3337/1785 1958/3339/1787 1959/3340/1788 +f 1957/3338/1786 1960/3341/1789 1961/3342/1790 +f 1957/3338/1786 1961/3342/1790 1958/3339/1787 +f 1960/3341/1789 1962/3343/1791 1963/3344/1792 +f 1960/3341/1789 1963/3344/1792 1961/3342/1790 +f 1962/3343/1791 1964/3345/1793 1965/3346/1794 +f 1962/3343/1791 1965/3346/1794 1963/3344/1792 +f 1964/3345/1793 1966/3347/1795 1967/3348/1796 +f 1964/3345/1793 1967/3348/1796 1965/3346/1794 +f 1966/3347/1795 1968/3349/1797 1969/3350/1798 +f 1966/3347/1795 1969/3350/1798 1967/3348/1796 +f 1968/3349/1797 1970/3351/1799 1971/3352/1800 +f 1968/3349/1797 1971/3352/1800 1969/3350/1798 +f 1970/3351/1799 1972/3353/1801 1973/3354/1802 +f 1970/3351/1799 1973/3354/1802 1971/3352/1800 +f 1972/3353/1801 1974/3355/1803 1975/3356/1804 +f 1972/3353/1801 1975/3356/1804 1973/3354/1802 +f 1976/3357/1805 1956/3337/1785 1959/3340/1788 +f 1976/3357/1805 1959/3340/1788 1977/3358/1804 +f 1981/905/2 1983/907/2 1978/902/2 +f 1998/3359/1806 1999/3360/1807 2000/3361/1808 +f 1998/3359/1806 2000/3361/1808 2001/3362/1809 +f 1999/3360/1807 2002/3363/1810 2003/3364/1811 +f 1999/3360/1807 2003/3364/1811 2000/3361/1808 +f 2002/3363/1810 2004/3365/1812 2005/3366/1813 +f 2002/3363/1810 2005/3366/1813 2003/3364/1811 +f 2004/3365/1812 2006/3367/1814 2007/3368/1815 +f 2004/3365/1812 2007/3368/1815 2005/3366/1813 +f 2006/3367/1814 2008/3369/1816 2009/3370/1817 +f 2006/3367/1814 2009/3370/1817 2007/3368/1815 +f 2008/3369/1816 2010/3371/1818 2011/3372/1819 +f 2008/3369/1816 2011/3372/1819 2009/3370/1817 +f 2010/3371/1818 2012/3373/1820 2013/3374/1821 +f 2010/3371/1818 2013/3374/1821 2011/3372/1819 +f 2012/3373/1820 2014/3375/1822 2015/3376/1823 +f 2012/3373/1820 2015/3376/1823 2013/3374/1821 +f 2014/3375/1822 2016/3377/1824 2017/3378/1825 +f 2014/3375/1822 2017/3378/1825 2015/3376/1823 +f 2016/3377/1824 2018/3379/1806 2019/3380/1809 +f 2016/3377/1824 2019/3380/1809 2017/3378/1825 +f 2020/3381/1826 2021/3382/1827 2022/3383/1828 +f 2020/3381/1826 2022/3383/1828 2023/3384/1829 +f 2021/3382/1827 2024/3385/1830 2025/3386/1831 +f 2021/3382/1827 2025/3386/1831 2022/3383/1828 +f 2024/3385/1830 2026/3387/1832 2027/3388/1833 +f 2024/3385/1830 2027/3388/1833 2025/3386/1831 +f 2026/3387/1832 2028/3389/1834 2029/3390/1835 +f 2026/3387/1832 2029/3390/1835 2027/3388/1833 +f 2028/3389/1834 2030/3391/1836 2031/3392/1837 +f 2028/3389/1834 2031/3392/1837 2029/3390/1835 +f 2030/3391/1836 2032/3393/1838 2033/3394/1839 +f 2030/3391/1836 2033/3394/1839 2031/3392/1837 +f 2032/3393/1838 2034/3395/1840 2035/3396/1841 +f 2032/3393/1838 2035/3396/1841 2033/3394/1839 +f 2034/3395/1840 2036/3397/1842 2037/3398/1843 +f 2034/3395/1840 2037/3398/1843 2035/3396/1841 +f 2036/3397/1842 2038/3399/1844 2039/3400/1845 +f 2036/3397/1842 2039/3400/1845 2037/3398/1843 +f 2038/3399/1844 2020/3381/1826 2023/3384/1829 +f 2038/3399/1844 2023/3384/1829 2039/3400/1845 +f 2021/3382/1827 2020/3381/1826 2040/3401/1846 +f 2021/3382/1827 2040/3401/1846 2041/3402/1847 +f 2024/3385/1830 2021/3382/1827 2041/3402/1847 +f 2024/3385/1830 2041/3402/1847 2042/3403/1848 +f 2026/3387/1832 2024/3385/1830 2042/3403/1848 +f 2026/3387/1832 2042/3403/1848 2043/3404/1849 +f 2028/3389/1834 2026/3387/1832 2043/3404/1849 +f 2028/3389/1834 2043/3404/1849 2044/3405/1850 +f 2030/3391/1836 2028/3389/1834 2044/3405/1850 +f 2030/3391/1836 2044/3405/1850 2045/3406/1851 +f 2032/3393/1838 2030/3391/1836 2045/3406/1851 +f 2032/3393/1838 2045/3406/1851 2046/3407/1852 +f 2034/3395/1840 2032/3393/1838 2046/3407/1852 +f 2034/3395/1840 2046/3407/1852 2047/3408/1853 +f 2036/3397/1842 2034/3395/1840 2047/3408/1853 +f 2036/3397/1842 2047/3408/1853 2048/3409/1854 +f 2038/3399/1844 2036/3397/1842 2048/3409/1854 +f 2038/3399/1844 2048/3409/1854 2049/3410/1855 +f 2020/3381/1826 2038/3399/1844 2049/3410/1855 +f 2020/3381/1826 2049/3410/1855 2040/3401/1846 +f 2041/3402/1847 2040/3401/1846 2050/3411/1856 +f 2041/3402/1847 2050/3411/1856 2051/3412/1857 +f 2042/3403/1848 2041/3402/1847 2051/3412/1857 +f 2042/3403/1848 2051/3412/1857 2052/3413/1858 +f 2043/3404/1849 2042/3403/1848 2052/3413/1858 +f 2043/3404/1849 2052/3413/1858 2053/3414/1859 +f 2044/3405/1850 2043/3404/1849 2053/3414/1859 +f 2044/3405/1850 2053/3414/1859 2054/3415/1860 +f 2045/3406/1851 2044/3405/1850 2054/3415/1860 +f 2045/3406/1851 2054/3415/1860 2055/3416/1861 +f 2046/3407/1852 2045/3406/1851 2055/3416/1861 +f 2046/3407/1852 2055/3416/1861 2056/3417/1862 +f 2047/3408/1853 2046/3407/1852 2056/3417/1862 +f 2047/3408/1853 2056/3417/1862 2057/3418/1863 +f 2048/3409/1854 2047/3408/1853 2057/3418/1863 +f 2048/3409/1854 2057/3418/1863 2058/3419/1864 +f 2049/3410/1855 2048/3409/1854 2058/3419/1864 +f 2049/3410/1855 2058/3419/1864 2059/3420/1865 +f 2040/3401/1846 2049/3410/1855 2059/3420/1865 +f 2040/3401/1846 2059/3420/1865 2050/3411/1856 +f 2060/922/141 2061/3421/141 2062/923/141 +f 4642/3422/1866 2064/3423/1867 2065/3424/1866 +f 4642/3422/1866 2065/3424/1866 4646/3425/1868 +f 4644/3426/1869 2066/3427/1870 2067/3428/1871 +f 4644/3426/1869 2067/3428/1871 4655/3429/1872 +f 4640/3430/1873 2070/3431/1874 2071/3432/1875 +f 4640/3430/1873 2071/3432/1875 4645/3433/1876 +f 4639/3434/1877 2072/3435/1878 2073/3436/1877 +f 4639/3434/1877 2073/3436/1877 4638/3437/1879 +f 2080/935/1880 2082/937/1880 2083/938/1880 +f 2086/3438/1881 2087/3439/1882 2088/3440/1883 +f 2086/3438/1881 2088/3440/1883 2089/3441/1884 +f 2087/3439/1882 2090/3442/1885 2091/3443/1886 +f 2087/3439/1882 2091/3443/1886 2088/3440/1883 +f 2090/3442/1885 2092/3444/1887 2093/3445/1888 +f 2090/3442/1885 2093/3445/1888 2091/3443/1886 +f 2092/3444/1887 2094/3446/1889 2095/3447/1890 +f 2092/3444/1887 2095/3447/1890 2093/3445/1888 +f 2094/3446/1889 2096/3448/1891 2097/3449/1892 +f 2094/3446/1889 2097/3449/1892 2095/3447/1890 +f 2096/3448/1891 2098/3450/1893 2099/3451/1894 +f 2096/3448/1891 2099/3451/1894 2097/3449/1892 +f 2098/3450/1893 2100/3452/1895 2101/3453/1896 +f 2098/3450/1893 2101/3453/1896 2099/3451/1894 +f 2100/3452/1895 2102/3454/1897 2103/3455/1898 +f 2100/3452/1895 2103/3455/1898 2101/3453/1896 +f 2102/3454/1897 2104/3456/1899 2105/3457/1900 +f 2102/3454/1897 2105/3457/1900 2103/3455/1898 +f 2106/3458/1899 2086/3438/1881 2089/3441/1884 +f 2106/3458/1899 2089/3441/1884 2107/3459/1900 +f 2118/951/4 2119/957/4 2120/952/4 +f 2124/956/4 2123/955/4 2125/3460/4 +f 2128/3461/1901 2129/3462/1902 2130/3463/1903 +f 2128/3461/1901 2130/3463/1903 2131/3464/1904 +f 2129/3462/1902 2132/3465/1905 2133/3466/1906 +f 2129/3462/1902 2133/3466/1906 2130/3463/1903 +f 2132/3465/1905 2134/3467/1907 2135/3468/1908 +f 2132/3465/1905 2135/3468/1908 2133/3466/1906 +f 2134/3467/1907 2136/3469/1909 2137/3470/1910 +f 2134/3467/1907 2137/3470/1910 2135/3468/1908 +f 2136/3469/1909 2138/3471/1911 2139/3472/1912 +f 2136/3469/1909 2139/3472/1912 2137/3470/1910 +f 2138/3471/1911 2140/3473/1913 2141/3474/1914 +f 2138/3471/1911 2141/3474/1914 2139/3472/1912 +f 2140/3473/1913 2142/3475/1915 2143/3476/1916 +f 2140/3473/1913 2143/3476/1916 2141/3474/1914 +f 2142/3475/1915 2144/3477/1917 2145/3478/1918 +f 2142/3475/1915 2145/3478/1918 2143/3476/1916 +f 2144/3477/1917 2146/3479/1919 2147/3480/1920 +f 2144/3477/1917 2147/3480/1920 2145/3478/1918 +f 2146/3479/1919 2148/3481/1901 2149/3482/1904 +f 2146/3479/1919 2149/3482/1904 2147/3480/1920 +f 2150/3483/1921 2151/3484/1922 2152/3485/1923 +f 2150/3483/1921 2152/3485/1923 2153/3486/1924 +f 2151/3484/1922 2154/3487/1925 2155/3488/1926 +f 2151/3484/1922 2155/3488/1926 2152/3485/1923 +f 2154/3487/1925 2156/3489/1927 2157/3490/1928 +f 2154/3487/1925 2157/3490/1928 2155/3488/1926 +f 2156/3489/1927 2158/3491/1929 2159/3492/1930 +f 2156/3489/1927 2159/3492/1930 2157/3490/1928 +f 2158/3491/1929 2160/3493/1931 2161/3494/1932 +f 2158/3491/1929 2161/3494/1932 2159/3492/1930 +f 2160/3493/1931 2162/3495/1933 2163/3496/1934 +f 2160/3493/1931 2163/3496/1934 2161/3494/1932 +f 2162/3495/1933 2164/3497/1935 2165/3498/1936 +f 2162/3495/1933 2165/3498/1936 2163/3496/1934 +f 2164/3497/1935 2166/3499/1937 2167/3500/1938 +f 2164/3497/1935 2167/3500/1938 2165/3498/1936 +f 2166/3499/1937 2168/3501/1939 2169/3502/1940 +f 2166/3499/1937 2169/3502/1940 2167/3500/1938 +f 2168/3501/1939 2150/3483/1921 2153/3486/1924 +f 2168/3501/1939 2153/3486/1924 2169/3502/1940 +f 2151/3484/1922 2150/3483/1921 2170/3503/1941 +f 2151/3484/1922 2170/3503/1941 2171/3504/1942 +f 2154/3487/1925 2151/3484/1922 2171/3504/1942 +f 2154/3487/1925 2171/3504/1942 2172/3505/1943 +f 2156/3489/1927 2154/3487/1925 2172/3505/1943 +f 2156/3489/1927 2172/3505/1943 2173/3506/1944 +f 2158/3491/1929 2156/3489/1927 2173/3506/1944 +f 2158/3491/1929 2173/3506/1944 2174/3507/1945 +f 2160/3493/1931 2158/3491/1929 2174/3507/1945 +f 2160/3493/1931 2174/3507/1945 2175/3508/1946 +f 2162/3495/1933 2160/3493/1931 2175/3508/1946 +f 2162/3495/1933 2175/3508/1946 2176/3509/1947 +f 2164/3497/1935 2162/3495/1933 2176/3509/1947 +f 2164/3497/1935 2176/3509/1947 2177/3510/1948 +f 2166/3499/1937 2164/3497/1935 2177/3510/1948 +f 2166/3499/1937 2177/3510/1948 2178/3511/1949 +f 2168/3501/1939 2166/3499/1937 2178/3511/1949 +f 2168/3501/1939 2178/3511/1949 2179/3512/1950 +f 2150/3483/1921 2168/3501/1939 2179/3512/1950 +f 2150/3483/1921 2179/3512/1950 2170/3503/1941 +f 2171/3504/1942 2170/3503/1941 2180/3513/1951 +f 2171/3504/1942 2180/3513/1951 2181/3514/1952 +f 2172/3505/1943 2171/3504/1942 2181/3514/1952 +f 2172/3505/1943 2181/3514/1952 2182/3515/1953 +f 2173/3506/1944 2172/3505/1943 2182/3515/1953 +f 2173/3506/1944 2182/3515/1953 2183/3516/1954 +f 2174/3507/1945 2173/3506/1944 2183/3516/1954 +f 2174/3507/1945 2183/3516/1954 2184/3517/1955 +f 2175/3508/1946 2174/3507/1945 2184/3517/1955 +f 2175/3508/1946 2184/3517/1955 2185/3518/1956 +f 2176/3509/1947 2175/3508/1946 2185/3518/1956 +f 2176/3509/1947 2185/3518/1956 2186/3519/1957 +f 2177/3510/1948 2176/3509/1947 2186/3519/1957 +f 2177/3510/1948 2186/3519/1957 2187/3520/1958 +f 2178/3511/1949 2177/3510/1948 2187/3520/1958 +f 2178/3511/1949 2187/3520/1958 2188/3521/1959 +f 2179/3512/1950 2178/3511/1949 2188/3521/1959 +f 2179/3512/1950 2188/3521/1959 2189/3522/1960 +f 2170/3503/1941 2179/3512/1950 2189/3522/1960 +f 2170/3503/1941 2189/3522/1960 2180/3513/1951 +f 3693/3523/1961 2194/3524/1962 2195/3525/1961 +f 3693/3523/1961 2195/3525/1961 3694/3526/1963 +f 3696/3527/1964 2196/3528/1965 2197/3529/1966 +f 3696/3527/1964 2197/3529/1966 3708/3530/1967 +f 3684/3531/1968 2200/3532/1969 2201/3533/1970 +f 3684/3531/1968 2201/3533/1970 3634/3534/1971 +f 3681/3535/1972 2202/3536/1973 2203/3537/1972 +f 3681/3535/1972 2203/3537/1972 3627/3538/1974 +f 2208/972/149 2209/3539/149 2207/971/149 +f 2210/973/150 2211/3540/150 2212/3541/150 +f 2210/973/150 2212/3541/150 2213/974/150 +f 2210/973/150 2214/975/150 2215/3542/150 +f 2216/3543/1975 2217/3544/1976 2218/3545/1976 +f 2216/3543/1975 2218/3545/1976 2219/3546/1977 +f 2220/3547/1978 2221/3548/1979 2222/3549/1980 +f 2220/3547/1978 2222/3549/1980 2223/3550/1981 +f 2221/3548/1979 2224/3551/1982 2225/3552/1983 +f 2221/3548/1979 2225/3552/1983 2222/3549/1980 +f 2224/3551/1982 2226/3553/1984 2227/3554/1985 +f 2224/3551/1982 2227/3554/1985 2225/3552/1983 +f 2226/3553/1984 2228/3555/1986 2229/3556/1987 +f 2226/3553/1984 2229/3556/1987 2227/3554/1985 +f 2228/3555/1986 2216/3543/1975 2219/3546/1977 +f 2228/3555/1986 2219/3546/1977 2229/3556/1987 +f 2230/3557/1988 2231/3558/1989 2232/3559/1990 +f 2230/3557/1988 2232/3559/1990 2233/3560/1991 +f 2234/3561/1992 2230/3557/1988 2233/3560/1991 +f 2234/3561/1992 2233/3560/1991 2235/3562/1993 +f 2236/3563/1994 2234/3561/1992 2235/3562/1993 +f 2236/3563/1994 2235/3562/1993 2237/3564/1995 +f 2238/3565/1996 2236/3563/1994 2237/3564/1995 +f 2238/3565/1996 2237/3564/1995 2239/3566/1997 +f 2240/3567/1998 2241/3568/1999 2242/3569/2000 +f 2240/3567/1998 2242/3569/2000 2243/3570/2001 +f 2231/3558/1989 2240/3567/1998 2243/3570/2001 +f 2231/3558/1989 2243/3570/2001 2232/3559/1990 +f 2244/976/2 3665/978/2 3623/3571/2 +f 2233/3560/1991 2232/3559/1990 2251/3572/2002 +f 2233/3560/1991 2251/3572/2002 2252/3573/2003 +f 2235/3562/1993 2233/3560/1991 2252/3573/2003 +f 2235/3562/1993 2252/3573/2003 2253/3574/2004 +f 2237/3564/1995 2235/3562/1993 2253/3574/2004 +f 2237/3564/1995 2253/3574/2004 2254/3575/2005 +f 2239/3566/1997 2237/3564/1995 2254/3575/2005 +f 2239/3566/1997 2254/3575/2005 2255/3576/2006 +f 2256/3577/2007 2257/3578/2008 2258/3579/2009 +f 2256/3577/2007 2258/3579/2009 2259/3580/2010 +f 2242/3569/2000 2256/3577/2007 2259/3580/2010 +f 2242/3569/2000 2259/3580/2010 2260/3581/2011 +f 2243/3570/2001 2242/3569/2000 2260/3581/2011 +f 2243/3570/2001 2260/3581/2011 2261/3582/2012 +f 2232/3559/1990 2243/3570/2001 2261/3582/2012 +f 2232/3559/1990 2261/3582/2012 2251/3572/2002 +f 2268/3583/2013 2269/3584/2014 2270/3585/2015 +f 2268/3583/2013 2270/3585/2015 2271/3586/2015 +f 2272/3587/2016 2273/3588/2017 2274/3589/2018 +f 2272/3587/2016 2274/3589/2018 2275/3590/2019 +f 2275/3590/2019 2274/3589/2018 2276/3591/2020 +f 2275/3590/2019 2276/3591/2020 2277/3592/2021 +f 2277/3592/2021 2276/3591/2020 2278/3593/2022 +f 2277/3592/2021 2278/3593/2022 2279/3594/2023 +f 2279/3594/2023 2278/3593/2022 2280/3595/2024 +f 2279/3594/2023 2280/3595/2024 2281/3596/2025 +f 2281/3596/2025 2280/3595/2024 2269/3584/2014 +f 2281/3596/2025 2269/3584/2014 2268/3583/2013 +f 2282/3597/2026 2283/3598/2027 2284/3599/2028 +f 2282/3597/2026 2284/3599/2028 2285/3600/2029 +f 2286/3601/2030 2287/3602/2031 2283/3598/2027 +f 2286/3601/2030 2283/3598/2027 2282/3597/2026 +f 2288/3603/2032 2289/3604/2033 2287/3602/2031 +f 2288/3603/2032 2287/3602/2031 2286/3601/2030 +f 2290/3605/2034 2291/3606/2035 2289/3604/2033 +f 2290/3605/2034 2289/3604/2033 2288/3603/2032 +f 2292/3607/2036 2293/3608/2037 2294/3609/2038 +f 2292/3607/2036 2294/3609/2038 2295/3610/2039 +f 2296/3611/2040 2297/3612/2041 2293/3608/2037 +f 2296/3611/2040 2293/3608/2037 2292/3607/2036 +f 2298/3613/2042 2299/3614/2043 2297/3612/2041 +f 2298/3613/2042 2297/3612/2041 2296/3611/2040 +f 2285/3600/2029 2284/3599/2028 2299/3614/2043 +f 2285/3600/2029 2299/3614/2043 2298/3613/2042 +f 2300/995/2 4624/3615/2044 4621/996/2 +f 2302/3616/2045 2303/3617/2046 4624/3615/2044 +f 2302/3616/2045 4624/3615/2044 2300/995/2 +f 2304/3618/2047 2305/3619/2 2303/3617/2046 +f 2304/3618/2047 2303/3617/2046 2302/3616/2045 +f 2306/1001/2 4647/1000/2 4668/3620/2048 +f 2306/1001/2 4668/3620/2048 2307/3621/2 +f 2283/3598/2027 2311/3622/2049 2312/3623/2050 +f 2283/3598/2027 2312/3623/2050 2284/3599/2028 +f 2287/3602/2031 2313/3624/2051 2311/3622/2049 +f 2287/3602/2031 2311/3622/2049 2283/3598/2027 +f 2289/3604/2033 2314/3625/2052 2313/3624/2051 +f 2289/3604/2033 2313/3624/2051 2287/3602/2031 +f 2291/3606/2035 2315/3626/2053 2314/3625/2052 +f 2291/3606/2035 2314/3625/2052 2289/3604/2033 +f 2293/3608/2037 2316/3627/2054 2317/3628/2055 +f 2293/3608/2037 2317/3628/2055 2294/3609/2038 +f 2297/3612/2041 2318/3629/2056 2316/3627/2054 +f 2297/3612/2041 2316/3627/2054 2293/3608/2037 +f 2299/3614/2043 2319/3630/2057 2318/3629/2056 +f 2299/3614/2043 2318/3629/2056 2297/3612/2041 +f 2284/3599/2028 2312/3623/2050 2319/3630/2057 +f 2284/3599/2028 2319/3630/2057 2299/3614/2043 +f 2320/3631/2058 4666/3632/2058 4664/3633/2059 +f 2320/3631/2058 4664/3633/2059 2321/3634/2060 +f 2322/3635/2061 4664/3633/2062 4665/3636/815 +f 2322/3635/2061 4665/3636/815 2323/3637/815 +f 2324/3638/2063 2328/3639/2064 2329/3640/2065 +f 2324/3638/2063 2329/3640/2065 2325/3641/2048 +f 2336/3642/1975 2337/3643/2066 2338/3644/2067 +f 2336/3642/1975 2338/3644/2067 2339/3645/2068 +f 2337/3643/2066 2340/3646/2069 2341/3647/2070 +f 2337/3643/2066 2341/3647/2070 2338/3644/2067 +f 2340/3646/2069 2342/3648/2071 2343/3649/2072 +f 2340/3646/2069 2343/3649/2072 2341/3647/2070 +f 2344/3650/2073 2345/3651/1979 2346/3652/1980 +f 2344/3650/2073 2346/3652/1980 2347/3653/1981 +f 2345/3651/1979 2348/3654/1982 2349/3655/1983 +f 2345/3651/1979 2349/3655/1983 2346/3652/1980 +f 2348/3654/1982 2350/3656/1984 2351/3657/1985 +f 2348/3654/1982 2351/3657/1985 2349/3655/1983 +f 2350/3656/1984 2352/3658/1986 2353/3659/1987 +f 2350/3656/1984 2353/3659/1987 2351/3657/1985 +f 2352/3658/1986 2336/3642/1975 2339/3645/2068 +f 2352/3658/1986 2339/3645/2068 2353/3659/1987 +f 2354/3660/1988 2355/3661/1989 2356/3662/1990 +f 2354/3660/1988 2356/3662/1990 2357/3663/1991 +f 2358/3664/1992 2354/3660/1988 2357/3663/1991 +f 2358/3664/1992 2357/3663/1991 2359/3665/1993 +f 2360/3666/1994 2358/3664/1992 2359/3665/1993 +f 2360/3666/1994 2359/3665/1993 2361/3667/1995 +f 2362/3668/2074 2360/3666/1994 2361/3667/1995 +f 2362/3668/2074 2361/3667/1995 2363/3669/1997 +f 2364/3670/2075 2365/3671/2076 2366/3672/2077 +f 2364/3670/2075 2366/3672/2077 2367/3673/2078 +f 2368/3674/2079 2364/3670/2075 2367/3673/2078 +f 2368/3674/2079 2367/3673/2078 2369/3675/2080 +f 2370/3676/1998 2368/3674/2079 2369/3675/2080 +f 2370/3676/1998 2369/3675/2080 2371/3677/2001 +f 2355/3661/1989 2370/3676/1998 2371/3677/2001 +f 2355/3661/1989 2371/3677/2001 2356/3662/1990 +f 2372/1016/2 3667/1018/2 3625/3678/2081 +f 2374/3679/2082 2372/1016/2 3625/3678/2081 +f 2374/3679/2082 3625/3678/2081 3582/3680/2083 +f 2375/3681/2084 2374/3679/2082 3582/3680/2083 +f 2375/3681/2084 3582/3680/2083 3579/3682/2 +f 2378/1023/2 3584/1022/2 3626/1025/2 +f 2357/3663/1991 2356/3662/1990 2381/3683/2002 +f 2357/3663/1991 2381/3683/2002 2382/3684/2003 +f 2359/3665/1993 2357/3663/1991 2382/3684/2003 +f 2359/3665/1993 2382/3684/2003 2383/3685/2004 +f 2361/3667/1995 2359/3665/1993 2383/3685/2004 +f 2361/3667/1995 2383/3685/2004 2384/3686/2005 +f 2363/3669/1997 2361/3667/1995 2384/3686/2005 +f 2363/3669/1997 2384/3686/2005 2385/3687/2006 +f 2367/3673/2078 2366/3672/2077 2386/3688/2009 +f 2367/3673/2078 2386/3688/2009 2387/3689/2010 +f 2369/3675/2080 2367/3673/2078 2387/3689/2010 +f 2369/3675/2080 2387/3689/2010 2388/3690/2011 +f 2371/3677/2001 2369/3675/2080 2388/3690/2011 +f 2371/3677/2001 2388/3690/2011 2389/3691/2012 +f 2356/3662/1990 2371/3677/2001 2389/3691/2012 +f 2356/3662/1990 2389/3691/2012 2381/3683/2002 +f 2394/1033/2 2391/1030/2 2395/1034/2 +f 2396/3692/2085 2397/3693/2086 2398/3694/2087 +f 2396/3692/2085 2398/3694/2087 2399/3695/2088 +f 2404/3696/2089 2405/3697/2090 2406/3698/2091 +f 2404/3696/2089 2406/3698/2091 2407/3699/2092 +f 2406/3698/2091 2405/3697/2090 2410/3700/2093 +f 2406/3698/2091 2410/3700/2093 2411/3701/2094 +f 2398/3694/2087 2397/3693/2086 3713/3702/2095 +f 2398/3694/2087 3713/3702/2095 3714/3703/2096 +f 2399/3695/2088 2398/3694/2087 2412/3704/2097 +f 2399/3695/2088 2412/3704/2097 2413/3705/2098 +f 2398/3694/2087 3714/3703/2096 2414/3706/2099 +f 2398/3694/2087 2414/3706/2099 2412/3704/2097 +f 2407/3699/2092 2406/3698/2091 2415/3707/2100 +f 2407/3699/2092 2415/3707/2100 3699/3708/2101 +f 2406/3698/2091 2411/3701/2094 2416/3709/2102 +f 2406/3698/2091 2416/3709/2102 2415/3707/2100 +f 2397/3693/2086 2396/3692/2085 2417/3710/2103 +f 2397/3693/2086 2417/3710/2103 2418/3711/2104 +f 2410/3700/2093 2405/3697/2090 2419/3712/2105 +f 2410/3700/2093 2419/3712/2105 2420/3713/2106 +f 2405/3697/2090 2404/3696/2089 2421/3714/2107 +f 2405/3697/2090 2421/3714/2107 2419/3712/2105 +f 3713/3702/2095 2397/3693/2086 2418/3711/2104 +f 3713/3702/2095 2418/3711/2104 3698/3715/2108 +f 2422/3716/2109 2423/3717/2110 2424/3718/2111 +f 2422/3716/2109 2424/3718/2111 2425/3719/2112 +f 2428/3720/2113 2429/3721/2114 2430/3722/2115 +f 2428/3720/2113 2430/3722/2115 2431/3723/2116 +f 2434/3724/2089 2435/3725/2090 2436/3726/2091 +f 2434/3724/2089 2436/3726/2091 2437/3727/2092 +f 2436/3726/2091 2435/3725/2090 2440/3728/2093 +f 2436/3726/2091 2440/3728/2093 2441/3729/2094 +f 2430/3722/2115 2429/3721/2114 3660/3730/2117 +f 2430/3722/2115 3660/3730/2117 3659/3731/2118 +f 2424/3718/2111 2423/3717/2110 3661/3732/2095 +f 2424/3718/2111 3661/3732/2095 3662/3733/2096 +f 2425/3719/2112 2424/3718/2111 2442/3734/2119 +f 2425/3719/2112 2442/3734/2119 2443/3735/2120 +f 2429/3721/2114 2428/3720/2113 2444/3736/2121 +f 2429/3721/2114 2444/3736/2121 2445/3737/2122 +f 2424/3718/2111 3662/3733/2096 2446/3738/2099 +f 2424/3718/2111 2446/3738/2099 2442/3734/2119 +f 2437/3727/2092 2436/3726/2091 2447/3739/2100 +f 2437/3727/2092 2447/3739/2100 3648/3740/2101 +f 2436/3726/2091 2441/3729/2094 2448/3741/2102 +f 2436/3726/2091 2448/3741/2102 2447/3739/2100 +f 3660/3730/2117 2429/3721/2114 2445/3737/2122 +f 3660/3730/2117 2445/3737/2122 3646/3742/2123 +f 2423/3717/2110 2422/3716/2109 2449/3743/2124 +f 2423/3717/2110 2449/3743/2124 2450/3744/2125 +f 2431/3723/2116 2430/3722/2115 2451/3745/2126 +f 2431/3723/2116 2451/3745/2126 2452/3746/2127 +f 2430/3722/2115 3659/3731/2118 2453/3747/2128 +f 2430/3722/2115 2453/3747/2128 2451/3745/2126 +f 2440/3728/2093 2435/3725/2090 2454/3748/2105 +f 2440/3728/2093 2454/3748/2105 3645/3749/2106 +f 2435/3725/2090 2434/3724/2089 2455/3750/2107 +f 2435/3725/2090 2455/3750/2107 2454/3748/2105 +f 3661/3732/2095 2423/3717/2110 2450/3744/2125 +f 3661/3732/2095 2450/3744/2125 3647/3751/2108 +f 2456/3752/6 2457/3753/2129 2458/3754/2130 +f 2456/3752/6 2458/3754/2130 2459/3755/6 +f 2460/3756/2131 2456/3752/6 2459/3755/6 +f 2460/3756/2131 2459/3755/6 2461/3757/2132 +f 2462/3758/2133 2463/3759/2133 2458/3754/2130 +f 2462/3758/2133 2458/3754/2130 2457/3753/2129 +f 2460/3756/2131 2461/3757/2132 2464/3760/2134 +f 2460/3756/2131 2464/3760/2134 2465/3761/2134 +f 2466/3762/2135 2467/3763/2136 2468/3764/2137 +f 2466/3762/2135 2468/3764/2137 2469/3765/2138 +f 2474/3766/2139 2475/3767/2140 2476/3768/2141 +f 2474/3766/2139 2476/3768/2141 2477/3769/2142 +f 2471/1048/2 256/59/2 254/57/2 +f 2476/3768/2141 2480/3770/2143 2481/3771/2144 +f 2476/3768/2141 2481/3771/2144 2477/3769/2142 +f 2468/3764/2137 4615/3772/2145 4614/3773/2146 +f 2468/3764/2137 4614/3773/2146 2469/3765/2138 +f 2467/3763/2136 2482/3774/2147 2483/3775/2148 +f 2467/3763/2136 2483/3775/2148 2468/3764/2137 +f 2468/3764/2137 2483/3775/2148 2484/3776/2149 +f 2468/3764/2137 2484/3776/2149 4615/3772/2145 +f 2475/3767/2140 4617/3777/2150 2485/3778/2151 +f 2475/3767/2140 2485/3778/2151 2476/3768/2141 +f 2476/3768/2141 2485/3778/2151 2486/3779/2152 +f 2476/3768/2141 2486/3779/2152 2480/3770/2143 +f 2469/3765/2138 2487/3780/2153 2488/3781/2154 +f 2469/3765/2138 2488/3781/2154 2466/3762/2135 +f 2481/3771/2144 2489/3782/2155 2490/3783/2156 +f 2481/3771/2144 2490/3783/2156 2477/3769/2142 +f 2477/3769/2142 2490/3783/2156 2491/3784/2157 +f 2477/3769/2142 2491/3784/2157 2474/3766/2139 +f 4614/3773/2146 4616/3785/2158 2487/3780/2153 +f 4614/3773/2146 2487/3780/2153 2469/3765/2138 +f 2492/3786/5 2493/3787/5 2494/3788/2159 +f 2492/3786/5 2494/3788/2159 2495/3789/2160 +f 2496/3790/2161 2497/3791/2162 2493/3787/5 +f 2496/3790/2161 2493/3787/5 2492/3786/5 +f 2498/3792/2163 2495/3789/2160 2494/3788/2159 +f 2498/3792/2163 2494/3788/2159 2499/3793/2163 +f 2496/3790/2161 2500/3794/2164 2501/3795/2164 +f 2496/3790/2161 2501/3795/2164 2497/3791/2162 +f 2514/3796/2165 2515/3797/2166 2516/3798/2167 +f 2514/3796/2165 2516/3798/2167 2517/3799/2165 +f 2515/3797/2166 2520/3800/2168 2521/3801/2168 +f 2515/3797/2166 2521/3801/2168 2516/3798/2167 +f 2526/3802/2169 2527/3803/2170 2528/3804/2171 +f 2526/3802/2169 2528/3804/2171 2529/3805/2172 +f 2529/3805/2172 2528/3804/2171 2530/3806/2173 +f 2529/3805/2172 2530/3806/2173 2531/3807/2174 +f 2531/3807/2174 2530/3806/2173 2532/3808/2175 +f 2531/3807/2174 2532/3808/2175 2533/3809/2176 +f 2534/3810/2177 2535/3811/2178 2536/3812/2179 +f 2534/3810/2177 2536/3812/2179 2537/3813/2180 +f 2535/3811/2178 2538/3814/2181 2539/3815/2182 +f 2535/3811/2178 2539/3815/2182 2536/3812/2179 +f 2538/3814/2181 2540/3816/2183 2541/3817/2184 +f 2538/3814/2181 2541/3817/2184 2539/3815/2182 +f 2542/3818/2185 2543/3819/2186 2544/3820/2187 +f 2542/3818/2185 2544/3820/2187 2545/3821/2185 +f 2546/3822/2188 2547/3823/2189 2548/3824/2189 +f 2546/3822/2188 2548/3824/2189 2549/3825/2190 +f 2543/3819/2186 2550/3826/2191 2551/3827/2192 +f 2543/3819/2186 2551/3827/2192 2544/3820/2187 +f 2552/3828/2193 2546/3822/2188 2549/3825/2190 +f 2552/3828/2193 2549/3825/2190 2553/3829/2194 +f 2550/3826/2191 2554/3830/2195 2555/3831/2195 +f 2550/3826/2191 2555/3831/2195 2551/3827/2192 +f 2556/3832/2196 2552/3828/2193 2553/3829/2194 +f 2556/3832/2196 2553/3829/2194 2557/3833/2196 +f 3720/3834/2197 3878/3835/2198 2562/3836/2199 +f 3720/3834/2197 2562/3836/2199 2563/3837/2197 +f 2566/3838/2200 2567/3839/2201 2568/3840/4 +f 2566/3838/2200 2568/3840/4 2569/3841/2200 +f 3878/3835/2198 4077/3842/2202 4075/3843/2202 +f 3878/3835/2198 4075/3843/2202 2562/3836/2199 +f 2568/3840/4 2567/3839/2201 2577/3844/2203 +f 2568/3840/4 2577/3844/2203 2578/3845/2203 +f 2592/3846/2165 2593/3847/2166 2594/3848/2167 +f 2592/3846/2165 2594/3848/2167 2595/3849/2165 +f 2593/3847/2166 2598/3850/2168 2599/3851/2168 +f 2593/3847/2166 2599/3851/2168 2594/3848/2167 +f 2604/3852/2204 2605/3853/2170 2606/3854/2171 +f 2604/3852/2204 2606/3854/2171 2607/3855/2172 +f 2607/3855/2172 2606/3854/2171 2608/3856/2205 +f 2607/3855/2172 2608/3856/2205 2609/3857/2174 +f 2609/3857/2174 2608/3856/2205 2610/3858/2175 +f 2609/3857/2174 2610/3858/2175 2611/3859/2176 +f 2612/3860/2177 2613/3861/2178 2614/3862/2179 +f 2612/3860/2177 2614/3862/2179 2615/3863/2206 +f 2613/3861/2178 2616/3864/2181 2617/3865/2207 +f 2613/3861/2178 2617/3865/2207 2614/3862/2179 +f 2616/3864/2181 2618/3866/2183 2619/3867/2184 +f 2616/3864/2181 2619/3867/2184 2617/3865/2207 +f 2620/3868/2185 2621/3869/2186 2622/3870/2187 +f 2620/3868/2185 2622/3870/2187 2623/3871/2185 +f 2624/3872/2188 2625/3873/2189 2626/3874/2189 +f 2624/3872/2188 2626/3874/2189 2627/3875/2190 +f 2621/3869/2186 2628/3876/2191 2629/3877/2192 +f 2621/3869/2186 2629/3877/2192 2622/3870/2187 +f 2630/3878/2193 2624/3872/2188 2627/3875/2190 +f 2630/3878/2193 2627/3875/2190 2631/3879/2194 +f 2628/3876/2191 2632/3880/2195 2633/3881/2195 +f 2628/3876/2191 2633/3881/2195 2629/3877/2192 +f 2634/3882/2196 2630/3878/2193 2631/3879/2194 +f 2634/3882/2196 2631/3879/2194 2635/3883/2196 +f 277/80/5 2640/1125/5 2641/3884/5 +f 279/82/6 2646/3885/6 2647/1130/6 +f 2648/3886/2208 2649/3887/2209 2650/3888/2210 +f 2648/3886/2208 2650/3888/2210 2651/3889/2208 +f 2649/3887/2209 2654/3890/2211 2655/3891/2211 +f 2649/3887/2209 2655/3891/2211 2650/3888/2210 +f 2660/3892/2212 2661/3893/2213 2662/3894/2214 +f 2660/3892/2212 2662/3894/2214 2663/3895/2215 +f 2663/3895/2215 2662/3894/2214 2664/3896/2216 +f 2663/3895/2215 2664/3896/2216 2665/3897/2217 +f 2665/3897/2217 2664/3896/2216 2666/3898/2218 +f 2665/3897/2217 2666/3898/2218 2667/3899/2219 +f 2668/3900/2220 2669/3901/2221 2670/3902/2222 +f 2668/3900/2220 2670/3902/2222 2671/3903/2223 +f 2669/3901/2221 2672/3904/2224 2673/3905/2225 +f 2669/3901/2221 2673/3905/2225 2670/3902/2222 +f 2672/3904/2224 2674/3906/2226 2675/3907/2227 +f 2672/3904/2224 2675/3907/2227 2673/3905/2225 +f 2676/3908/2228 2677/3909/2229 2678/3910/2230 +f 2676/3908/2228 2678/3910/2230 2679/3911/2228 +f 2680/3912/2231 2681/3913/2232 2682/3914/2232 +f 2680/3912/2231 2682/3914/2232 2683/3915/2233 +f 2677/3909/2229 2684/3916/2234 2685/3917/2235 +f 2677/3909/2229 2685/3917/2235 2678/3910/2230 +f 2686/3918/2236 2680/3912/2231 2683/3915/2233 +f 2686/3918/2236 2683/3915/2233 2687/3919/2237 +f 2684/3916/2234 2688/3920/2238 2689/3921/2238 +f 2684/3916/2234 2689/3921/2238 2685/3917/2235 +f 2690/3922/2239 2686/3918/2236 2687/3919/2237 +f 2690/3922/2239 2687/3919/2237 2691/3923/2239 +f 4507/3924/2240 4564/3925/2241 2696/3926/2242 +f 4507/3924/2240 2696/3926/2242 2697/3927/2240 +f 2700/3928/2243 2701/3929/2201 2702/3930/2244 +f 2700/3928/2243 2702/3930/2244 2703/3931/2243 +f 4564/3925/2241 4612/3932/2245 4610/3933/2245 +f 4564/3925/2241 4610/3933/2245 2696/3926/2242 +f 2702/3930/2244 2701/3929/2201 2711/3934/2246 +f 2702/3930/2244 2711/3934/2246 2712/3935/2246 +f 283/86/5 2718/1167/5 2719/3936/5 +f 285/88/6 2724/3937/6 2725/1172/6 +f 2726/3938/2208 2727/3939/2247 2728/3940/2210 +f 2726/3938/2208 2728/3940/2210 2729/3941/2208 +f 2730/3942/154 4589/3943/154 4605/3944/154 +f 2730/3942/154 4605/3944/154 2731/3945/154 +f 2727/3939/2247 2732/3946/2211 2733/3947/2211 +f 2727/3939/2247 2733/3947/2211 2728/3940/2210 +f 2738/3948/2212 2739/3949/2213 2740/3950/2248 +f 2738/3948/2212 2740/3950/2248 2741/3951/2249 +f 2741/3951/2249 2740/3950/2248 2742/3952/2250 +f 2741/3951/2249 2742/3952/2250 2743/3953/2217 +f 2743/3953/2217 2742/3952/2250 2744/3954/2218 +f 2743/3953/2217 2744/3954/2218 2745/3955/2219 +f 2746/3956/2220 2747/3957/2251 2748/3958/2252 +f 2746/3956/2220 2748/3958/2252 2749/3959/2223 +f 2747/3957/2251 2750/3960/2224 2751/3961/2253 +f 2747/3957/2251 2751/3961/2253 2748/3958/2252 +f 2750/3960/2224 2752/3962/2254 2753/3963/2255 +f 2750/3960/2224 2753/3963/2255 2751/3961/2253 +f 2754/3964/2228 2755/3965/2229 2756/3966/2230 +f 2754/3964/2228 2756/3966/2230 2757/3967/2228 +f 2758/3968/2231 2759/3969/2232 2760/3970/2232 +f 2758/3968/2231 2760/3970/2232 2761/3971/2233 +f 2755/3965/2229 2762/3972/2234 2763/3973/2235 +f 2755/3965/2229 2763/3973/2235 2756/3966/2230 +f 2764/3974/2236 2758/3968/2231 2761/3971/2233 +f 2764/3974/2236 2761/3971/2233 2765/3975/2237 +f 2762/3972/2234 2766/3976/2238 2767/3977/2238 +f 2762/3972/2234 2767/3977/2238 2763/3973/2235 +f 2768/3978/2239 2764/3974/2236 2765/3975/2237 +f 2768/3978/2239 2765/3975/2237 2769/3979/2239 +f 2770/3980/2256 2771/3981/2257 2772/3982/2258 +f 2770/3980/2256 2772/3982/2258 2773/3983/2259 +f 2771/3981/2257 2774/3984/2260 2775/3985/2261 +f 2771/3981/2257 2775/3985/2261 2772/3982/2258 +f 2774/3984/2260 2776/3986/2262 2777/3987/2263 +f 2774/3984/2260 2777/3987/2263 2775/3985/2261 +f 2776/3986/2262 2778/3988/2264 2779/3989/2265 +f 2776/3986/2262 2779/3989/2265 2777/3987/2263 +f 2778/3988/2264 2780/3990/2266 2781/3991/2267 +f 2778/3988/2264 2781/3991/2267 2779/3989/2265 +f 2782/3992/2266 2783/3993/2268 2784/3994/2269 +f 2782/3992/2266 2784/3994/2269 2785/3995/2270 +f 2783/3993/2268 2786/3996/2271 2787/3997/2272 +f 2783/3993/2268 2787/3997/2272 2784/3994/2269 +f 2786/3996/2271 2788/3998/2273 2789/3999/2274 +f 2786/3996/2271 2789/3999/2274 2787/3997/2272 +f 2788/3998/2273 2790/4000/2275 2791/4001/2276 +f 2788/3998/2273 2791/4001/2276 2789/3999/2274 +f 2790/4000/2275 2770/3980/2256 2773/3983/2259 +f 2790/4000/2275 2773/3983/2259 2791/4001/2276 +f 2787/3997/2272 2789/3999/2274 2792/4002/2277 +f 2787/3997/2272 2792/4002/2277 2793/4003/2278 +f 2784/3994/2269 2787/3997/2272 2793/4003/2278 +f 2784/3994/2269 2793/4003/2278 2794/4004/2279 +f 2785/3995/2270 2784/3994/2269 2794/4004/2279 +f 2785/3995/2270 2794/4004/2279 2795/4005/2280 +f 2779/3989/2265 2781/3991/2267 2796/4006/2280 +f 2779/3989/2265 2796/4006/2280 2797/4007/2281 +f 2777/3987/2263 2779/3989/2265 2797/4007/2281 +f 2777/3987/2263 2797/4007/2281 2798/4008/2282 +f 2775/3985/2261 2777/3987/2263 2798/4008/2282 +f 2775/3985/2261 2798/4008/2282 2799/4009/2283 +f 2772/3982/2258 2775/3985/2261 2799/4009/2283 +f 2772/3982/2258 2799/4009/2283 2800/4010/2284 +f 2773/3983/2259 2772/3982/2258 2800/4010/2284 +f 2773/3983/2259 2800/4010/2284 2801/4011/2285 +f 2791/4001/2276 2773/3983/2259 2801/4011/2285 +f 2791/4001/2276 2801/4011/2285 2802/4012/2286 +f 2789/3999/2274 2791/4001/2276 2802/4012/2286 +f 2789/3999/2274 2802/4012/2286 2792/4002/2277 +f 2807/4013/2287 2808/4014/2288 2809/4015/2289 +f 2807/4013/2287 2809/4015/2289 2810/4016/2290 +f 2808/4014/2288 2811/4017/2291 2812/4018/2292 +f 2808/4014/2288 2812/4018/2292 2809/4015/2289 +f 2811/4017/2291 2813/4019/2293 2814/4020/2294 +f 2811/4017/2291 2814/4020/2294 2812/4018/2292 +f 2813/4019/2293 2815/4021/2295 2816/4022/2296 +f 2813/4019/2293 2816/4022/2296 2814/4020/2294 +f 2815/4021/2295 2817/4023/2297 2818/4024/2298 +f 2815/4021/2295 2818/4024/2298 2816/4022/2296 +f 2819/4025/2297 2820/4026/2299 2821/4027/2300 +f 2819/4025/2297 2821/4027/2300 2822/4028/2301 +f 2820/4026/2299 2823/4029/2302 2824/4030/2303 +f 2820/4026/2299 2824/4030/2303 2821/4027/2300 +f 2823/4029/2302 2825/4031/2304 2826/4032/2305 +f 2823/4029/2302 2826/4032/2305 2824/4030/2303 +f 2825/4031/2304 2827/4033/2306 2828/4034/2307 +f 2825/4031/2304 2828/4034/2307 2826/4032/2305 +f 2827/4033/2306 2807/4013/2287 2810/4016/2290 +f 2827/4033/2306 2810/4016/2290 2828/4034/2307 +f 2824/4030/2303 2826/4032/2305 2829/4035/2308 +f 2824/4030/2303 2829/4035/2308 2830/4036/2309 +f 2821/4027/2300 2824/4030/2303 2830/4036/2309 +f 2821/4027/2300 2830/4036/2309 2831/4037/2310 +f 2822/4028/2301 2821/4027/2300 2831/4037/2310 +f 2822/4028/2301 2831/4037/2310 2832/4038/2311 +f 2816/4022/2296 2818/4024/2298 2833/4039/2311 +f 2816/4022/2296 2833/4039/2311 2834/4040/2312 +f 2814/4020/2294 2816/4022/2296 2834/4040/2312 +f 2814/4020/2294 2834/4040/2312 2835/4041/2313 +f 2812/4018/2292 2814/4020/2294 2835/4041/2313 +f 2812/4018/2292 2835/4041/2313 2836/4042/2314 +f 2809/4015/2289 2812/4018/2292 2836/4042/2314 +f 2809/4015/2289 2836/4042/2314 2837/4043/2315 +f 2810/4016/2290 2809/4015/2289 2837/4043/2315 +f 2810/4016/2290 2837/4043/2315 2838/4044/2316 +f 2828/4034/2307 2810/4016/2290 2838/4044/2316 +f 2828/4034/2307 2838/4044/2316 2839/4045/2317 +f 2826/4032/2305 2828/4034/2307 2839/4045/2317 +f 2826/4032/2305 2839/4045/2317 2829/4035/2308 +f 4499/4046/2318 2851/4047/2318 4509/4048/2318 +f 4499/4046/2318 4509/4048/2318 4511/4049/2318 +f 2856/1201/1 2858/1203/1 2859/4050/1 +f 2874/4051/2319 2875/4052/2320 2876/4053/2321 +f 2874/4051/2319 2876/4053/2321 2877/4054/2322 +f 2875/4052/2320 2878/4055/2323 2879/4056/2324 +f 2875/4052/2320 2879/4056/2324 2876/4053/2321 +f 2878/4055/2323 2880/4057/2325 2881/4058/2326 +f 2878/4055/2323 2881/4058/2326 2879/4056/2324 +f 2880/4057/2325 2882/4059/2327 2883/4060/2328 +f 2880/4057/2325 2883/4060/2328 2881/4058/2326 +f 2882/4059/2327 2884/4061/2329 2885/4062/2330 +f 2882/4059/2327 2885/4062/2330 2883/4060/2328 +f 2884/4061/2329 2886/4063/2331 2887/4064/2332 +f 2884/4061/2329 2887/4064/2332 2885/4062/2330 +f 2886/4063/2331 2888/4065/2333 2889/4066/2334 +f 2886/4063/2331 2889/4066/2334 2887/4064/2332 +f 2888/4065/2333 2890/4067/2335 2891/4068/2336 +f 2888/4065/2333 2891/4068/2336 2889/4066/2334 +f 2890/4067/2335 2892/4069/2337 2893/4070/2338 +f 2890/4067/2335 2893/4070/2338 2891/4068/2336 +f 2892/4069/2337 2894/4071/2319 2895/4072/2322 +f 2892/4069/2337 2895/4072/2322 2893/4070/2338 +f 2896/4073/2339 2897/4074/2340 2898/4075/2341 +f 2896/4073/2339 2898/4075/2341 2899/4076/2342 +f 2897/4074/2340 2900/4077/2343 2901/4078/2344 +f 2897/4074/2340 2901/4078/2344 2898/4075/2341 +f 2900/4077/2343 2902/4079/2345 2903/4080/2346 +f 2900/4077/2343 2903/4080/2346 2901/4078/2344 +f 2902/4079/2345 2904/4081/2347 2905/4082/2348 +f 2902/4079/2345 2905/4082/2348 2903/4080/2346 +f 2904/4081/2347 2906/4083/2349 2907/4084/2350 +f 2904/4081/2347 2907/4084/2350 2905/4082/2348 +f 2906/4083/2349 2908/4085/2351 2909/4086/2352 +f 2906/4083/2349 2909/4086/2352 2907/4084/2350 +f 2908/4085/2351 2910/4087/2353 2911/4088/2354 +f 2908/4085/2351 2911/4088/2354 2909/4086/2352 +f 2910/4087/2353 2912/4089/2355 2913/4090/2356 +f 2910/4087/2353 2913/4090/2356 2911/4088/2354 +f 2912/4089/2355 2914/4091/2357 2915/4092/2358 +f 2912/4089/2355 2915/4092/2358 2913/4090/2356 +f 2914/4091/2357 2916/4093/2359 2917/4094/2342 +f 2914/4091/2357 2917/4094/2342 2915/4092/2358 +f 4343/4095/2360 4293/4096/2361 2918/4097/2362 +f 4343/4095/2360 2918/4097/2362 2919/4098/2363 +f 4293/4096/2361 4228/4099/2364 2920/4100/2365 +f 4293/4096/2361 2920/4100/2365 2918/4097/2362 +f 4228/4099/2364 4195/4101/2366 2921/4102/2367 +f 4228/4099/2364 2921/4102/2367 2920/4100/2365 +f 4195/4101/2366 4211/4103/2368 2922/4104/2369 +f 4195/4101/2366 2922/4104/2369 2921/4102/2367 +f 4212/4105/2370 4260/4106/2371 2923/4107/2372 +f 4212/4105/2370 2923/4107/2372 4207/4108/2373 +f 4260/4106/2371 4335/4109/2374 2924/4110/2375 +f 4260/4106/2371 2924/4110/2375 2923/4107/2372 +f 4335/4109/2374 4370/4111/2376 2925/4112/2377 +f 4335/4109/2374 2925/4112/2377 2924/4110/2375 +f 4370/4111/2376 4393/4113/2378 2926/4114/2379 +f 4370/4111/2376 2926/4114/2379 2925/4112/2377 +f 4393/4113/2378 4384/4115/2380 2927/4116/2381 +f 4393/4113/2378 2927/4116/2381 2926/4114/2379 +f 4384/4115/2380 4344/4117/2382 2928/4118/2383 +f 4384/4115/2380 2928/4118/2383 2927/4116/2381 +f 2929/4119/2384 2930/4120/2385 4413/4121/2386 +f 2929/4119/2384 4413/4121/2386 4389/4122/2387 +f 2931/4123/2388 2929/4119/2384 4389/4122/2387 +f 2931/4123/2388 4389/4122/2387 4336/4124/2389 +f 2932/4125/2390 2931/4123/2388 4336/4124/2389 +f 2932/4125/2390 4336/4124/2389 4256/4126/2391 +f 2933/4127/2392 2932/4125/2390 4256/4126/2391 +f 2933/4127/2392 4256/4126/2391 4190/4128/2393 +f 2934/4129/2394 2933/4127/2392 4190/4128/2393 +f 2934/4129/2394 4190/4128/2393 4185/4130/2395 +f 2935/4131/2396 2934/4129/2394 4185/4130/2395 +f 2935/4131/2396 4185/4130/2395 4205/4132/2397 +f 2936/4133/2398 2935/4131/2396 4205/4132/2397 +f 2936/4133/2398 4205/4132/2397 4283/4134/2399 +f 2937/4135/2400 2936/4133/2398 4283/4134/2399 +f 2937/4135/2400 4283/4134/2399 4364/4136/2401 +f 2938/4137/2402 2939/4138/2403 4365/4139/2404 +f 2938/4137/2402 4365/4139/2404 4405/4140/2405 +f 2930/4120/2385 2938/4137/2402 4405/4140/2405 +f 2930/4120/2385 4405/4140/2405 4413/4121/2386 +f 2889/4066/2334 2891/4068/2336 2930/4120/2385 +f 2889/4066/2334 2930/4120/2385 2929/4119/2384 +f 2887/4064/2332 2889/4066/2334 2929/4119/2384 +f 2887/4064/2332 2929/4119/2384 2931/4123/2388 +f 2885/4062/2330 2887/4064/2332 2931/4123/2388 +f 2885/4062/2330 2931/4123/2388 2932/4125/2390 +f 2883/4060/2328 2885/4062/2330 2932/4125/2390 +f 2883/4060/2328 2932/4125/2390 2933/4127/2392 +f 2881/4058/2326 2883/4060/2328 2933/4127/2392 +f 2881/4058/2326 2933/4127/2392 2934/4129/2394 +f 2879/4056/2324 2881/4058/2326 2934/4129/2394 +f 2879/4056/2324 2934/4129/2394 2935/4131/2396 +f 2876/4053/2321 2879/4056/2324 2935/4131/2396 +f 2876/4053/2321 2935/4131/2396 2936/4133/2398 +f 2877/4054/2322 2876/4053/2321 2936/4133/2398 +f 2877/4054/2322 2936/4133/2398 2937/4135/2400 +f 2893/4070/2338 2895/4072/2322 2939/4138/2403 +f 2893/4070/2338 2939/4138/2403 2938/4137/2402 +f 2891/4068/2336 2893/4070/2338 2938/4137/2402 +f 2891/4068/2336 2938/4137/2402 2930/4120/2385 +f 2940/4141/2406 2941/4142/2406 2942/4143/2407 +f 2940/4141/2406 2942/4143/2407 2943/4144/2408 +f 2948/4145/2409 2949/4146/2410 2950/4147/2411 +f 2948/4145/2409 2950/4147/2411 2951/4148/2411 +f 4391/4149/2412 2952/4150/2413 2953/4151/2414 +f 4391/4149/2412 2953/4151/2414 4392/4152/2415 +f 2954/4153/2416 2955/4154/2417 2956/4155/2418 +f 2954/4153/2416 2956/4155/2418 2957/4156/2419 +f 2943/4144/2408 2942/4143/2407 2958/4157/2420 +f 2943/4144/2408 2958/4157/2420 2959/4158/2420 +f 4392/4152/2415 2953/4151/2414 2960/4159/2421 +f 4392/4152/2415 2960/4159/2421 4396/4160/2422 +f 2949/4146/2410 2948/4145/2409 2961/4161/2423 +f 2949/4146/2410 2961/4161/2423 2962/4162/2423 +f 2957/4156/2419 2956/4155/2418 2963/4163/2424 +f 2957/4156/2419 2963/4163/2424 2964/4164/2425 +f 4514/4165/2426 2957/4156/2419 2964/4164/2425 +f 4514/4165/2426 2964/4164/2425 4512/4166/2427 +f 4513/4167/2428 2954/4153/2416 2957/4156/2419 +f 4513/4167/2428 2957/4156/2419 4514/4165/2426 +f 2953/4151/2414 2952/4150/2413 4442/4168/2429 +f 2953/4151/2414 4442/4168/2429 4443/4169/2430 +f 2960/4159/2421 2953/4151/2414 4443/4169/2430 +f 2960/4159/2421 4443/4169/2430 4451/4170/2431 +f 2967/4171/2432 2968/4172/2432 2969/4173/2433 +f 2967/4171/2432 2969/4173/2433 2970/4174/2434 +f 2971/1230/162 4447/1232/162 4441/4175/162 +f 2973/1233/163 2974/4176/163 4501/1234/163 +f 2972/1231/162 2975/1238/162 2976/1241/162 +f 2972/1231/162 2976/1241/162 4447/1232/162 +f 2970/4174/2434 2969/4173/2433 4454/4177/2435 +f 2970/4174/2434 4454/4177/2435 2977/4178/2436 +f 2977/4178/2436 4454/4177/2435 4455/4179/2 +f 2977/4178/2436 4455/4179/2 2981/4180/2 +f 2983/4181/2 2984/4182/2 2985/4183/2437 +f 2983/4181/2 2985/4183/2437 2986/4184/2438 +f 2987/4185/164 2988/1244/164 4252/1247/164 +f 2987/4185/164 4252/1247/164 4089/4186/164 +f 2989/4187/165 2990/4188/165 4090/4189/165 +f 2989/4187/165 4090/4189/165 4254/1249/165 +f 2986/4184/2438 2985/4183/2437 4367/4190/2439 +f 2986/4184/2438 4367/4190/2439 2993/4191/2440 +f 2994/1248/165 2989/4187/165 4254/1249/165 +f 2991/1245/164 2996/1252/164 2992/1246/164 +f 2993/4191/2440 4367/4190/2439 4444/4192/2441 +f 2993/4191/2440 4444/4192/2441 2997/4193/2441 +f 2999/4194/2442 3000/4195/2442 3001/4196/2443 +f 2999/4194/2442 3001/4196/2443 3002/4197/2444 +f 3007/4198/2445 3008/4199/2446 3009/4200/2447 +f 3007/4198/2445 3009/4200/2447 3010/4201/2447 +f 4113/4202/2448 3011/4203/2449 3012/4204/2450 +f 4113/4202/2448 3012/4204/2450 4114/4205/2451 +f 3013/4206/2452 3014/4207/2453 3015/4208/2454 +f 3013/4206/2452 3015/4208/2454 3016/4209/2455 +f 3002/4197/2444 3001/4196/2443 3017/4210/2456 +f 3002/4197/2444 3017/4210/2456 3018/4211/2456 +f 4114/4205/2451 3012/4204/2450 3019/4212/2457 +f 4114/4205/2451 3019/4212/2457 4109/4213/2458 +f 3008/4199/2446 3007/4198/2445 3020/4214/2459 +f 3008/4199/2446 3020/4214/2459 3021/4215/2459 +f 3016/4209/2455 3015/4208/2454 3022/4216/2460 +f 3016/4209/2455 3022/4216/2460 3023/4217/2461 +f 4066/4218/2462 3016/4209/2455 3023/4217/2461 +f 4066/4218/2462 3023/4217/2461 4070/4219/2463 +f 4065/4220/2464 3013/4206/2452 3016/4209/2455 +f 4065/4220/2464 3016/4209/2455 4066/4218/2462 +f 3012/4204/2450 3011/4203/2449 4129/4221/2465 +f 3012/4204/2450 4129/4221/2465 4130/4222/2466 +f 3019/4212/2457 3012/4204/2450 4130/4222/2466 +f 3019/4212/2457 4130/4222/2466 4122/4223/2467 +f 3026/4224/2468 3027/4225/2468 3028/4226/2469 +f 3026/4224/2468 3028/4226/2469 3029/4227/2470 +f 3032/1265/6 3033/4228/6 4057/1266/6 +f 3029/4227/2470 3028/4226/2469 4094/4229/2471 +f 3029/4227/2470 4094/4229/2471 3036/4230/2472 +f 3034/1268/5 3038/4231/5 3039/4232/5 +f 3034/1268/5 3039/4232/5 3035/1269/5 +f 3036/4230/2472 4094/4229/2471 4093/4233/2 +f 3036/4230/2472 4093/4233/2 3040/4234/2 +f 3041/4235/6 3037/1270/6 4056/1271/6 +f 3041/4235/6 4056/1271/6 4055/4236/6 +f 3042/4237/2473 3043/4238/2473 3044/4239/2474 +f 3042/4237/2473 3044/4239/2474 3045/4240/2475 +f 3050/4241/2476 3051/4242/2477 3052/4243/2478 +f 3050/4241/2476 3052/4243/2478 3053/4244/2478 +f 4150/4245/2479 3054/4246/2480 3055/4247/2481 +f 4150/4245/2479 3055/4247/2481 4151/4248/2482 +f 3056/4249/2483 3057/4250/2484 3058/4251/2485 +f 3056/4249/2483 3058/4251/2485 3059/4252/2486 +f 3045/4240/2475 3044/4239/2474 3060/4253/2487 +f 3045/4240/2475 3060/4253/2487 3061/4254/2488 +f 4151/4248/2482 3055/4247/2481 3062/4255/2489 +f 4151/4248/2482 3062/4255/2489 4145/4256/2490 +f 3051/4242/2477 3050/4241/2476 3063/4257/2491 +f 3051/4242/2477 3063/4257/2491 3064/4258/2491 +f 3059/4252/2486 3058/4251/2485 3065/4259/2492 +f 3059/4252/2486 3065/4259/2492 3066/4260/2493 +f 4042/4261/2494 3059/4252/2486 3066/4260/2493 +f 4042/4261/2494 3066/4260/2493 4049/4262/2495 +f 4041/4263/2496 3056/4249/2483 3059/4252/2486 +f 4041/4263/2496 3059/4252/2486 4042/4261/2494 +f 3055/4247/2481 3054/4246/2480 4080/4264/2497 +f 3055/4247/2481 4080/4264/2497 4081/4265/2498 +f 3062/4255/2489 3055/4247/2481 4081/4265/2498 +f 3062/4255/2489 4081/4265/2498 4082/4266/2499 +f 3069/4267/2500 3070/4268/2500 3071/4269/2501 +f 3069/4267/2500 3071/4269/2501 3072/4270/2502 +f 3072/4270/2502 3071/4269/2501 4187/4271/2503 +f 3072/4270/2502 4187/4271/2503 3079/4272/2504 +f 3080/1288/167 3075/1282/167 4316/1285/167 +f 3079/4272/2504 4187/4271/2503 4092/4273/2 +f 3079/4272/2504 4092/4273/2 3083/4274/2 +f 3085/4275/2505 3086/4276/2505 3087/4277/2506 +f 3085/4275/2505 3087/4277/2506 3088/4278/2507 +f 3093/4279/2508 3094/4280/2509 3095/4281/2510 +f 3093/4279/2508 3095/4281/2510 3096/4282/2510 +f 4484/4283/2511 3097/4284/2512 3098/4285/2513 +f 4484/4283/2511 3098/4285/2513 4485/4286/2514 +f 3099/4287/2515 3100/4288/2516 3101/4289/2517 +f 3099/4287/2515 3101/4289/2517 3102/4290/2518 +f 3088/4278/2507 3087/4277/2506 3103/4291/2519 +f 3088/4278/2507 3103/4291/2519 3104/4292/2519 +f 4485/4286/2514 3098/4285/2513 3105/4293/2520 +f 4485/4286/2514 3105/4293/2520 4487/4294/2521 +f 3094/4280/2509 3093/4279/2508 3106/4295/2522 +f 3094/4280/2509 3106/4295/2522 3107/4296/2522 +f 3102/4290/2518 3101/4289/2517 3108/4297/2523 +f 3102/4290/2518 3108/4297/2523 3109/4298/2524 +f 4490/4299/2525 3102/4290/2518 3109/4298/2524 +f 4490/4299/2525 3109/4298/2524 4488/4300/2526 +f 4489/4301/2527 3099/4287/2515 3102/4290/2518 +f 4489/4301/2527 3102/4290/2518 4490/4299/2525 +f 3098/4285/2513 3097/4284/2512 4394/4302/2528 +f 3098/4285/2513 4394/4302/2528 4395/4303/2529 +f 3105/4293/2520 3098/4285/2513 4395/4303/2529 +f 3105/4293/2520 4395/4303/2529 4400/4304/2530 +f 3112/4305/2531 3113/4306/2532 3114/4307/2 +f 3112/4305/2531 3114/4307/2 3115/4308/2533 +f 3113/4306/2532 3116/4309/2534 3117/4310/2535 +f 3113/4306/2532 3117/4310/2535 3114/4307/2 +f 3115/4308/2533 3114/4307/2 3118/4311/2536 +f 3115/4308/2533 3118/4311/2536 3119/4312/2537 +f 3114/4307/2 3117/4310/2535 3120/4313/2538 +f 3114/4307/2 3120/4313/2538 3118/4311/2536 +f 3121/4314/2539 3122/4315/2540 3123/4316/2541 +f 3121/4314/2539 3123/4316/2541 3124/4317/2542 +f 3122/4315/2540 3125/4318/2543 3126/4319/2544 +f 3122/4315/2540 3126/4319/2544 3123/4316/2541 +f 3127/4320/2545 3128/4321/2546 3129/4322/2547 +f 3127/4320/2545 3129/4322/2547 3130/4323/2548 +f 3128/4321/2546 3131/4324/2549 3132/4325/2550 +f 3128/4321/2546 3132/4325/2550 3129/4322/2547 +f 3131/4324/2549 3133/4326/2551 3134/4327/2552 +f 3131/4324/2549 3134/4327/2552 3132/4325/2550 +f 3133/4326/2551 3135/4328/2553 3136/4329/2554 +f 3133/4326/2551 3136/4329/2554 3134/4327/2552 +f 3135/4328/2553 3137/4330/2555 3138/4331/2556 +f 3135/4328/2553 3138/4331/2556 3136/4329/2554 +f 3137/4330/2555 3139/4332/2557 3140/4333/2558 +f 3137/4330/2555 3140/4333/2558 3138/4331/2556 +f 3139/4332/2557 3141/4334/2559 3142/4335/2560 +f 3139/4332/2557 3142/4335/2560 3140/4333/2558 +f 3141/4334/2559 3143/4336/2561 3144/4337/2562 +f 3141/4334/2559 3144/4337/2562 3142/4335/2560 +f 3143/4336/2561 3145/4338/2563 3146/4339/2564 +f 3143/4336/2561 3146/4339/2564 3144/4337/2562 +f 3145/4338/2563 3121/4314/2539 3124/4317/2542 +f 3145/4338/2563 3124/4317/2542 3146/4339/2564 +f 3140/4333/2558 3142/4335/2560 3147/4340/2565 +f 3140/4333/2558 3147/4340/2565 3148/4341/2566 +f 3138/4331/2556 3140/4333/2558 3738/4342/2567 +f 3138/4331/2556 3738/4342/2567 3149/4343/2568 +f 3136/4329/2554 3138/4331/2556 3702/4344/2569 +f 3136/4329/2554 3702/4344/2569 3150/4345/2570 +f 3134/4327/2552 3136/4329/2554 3150/4345/2570 +f 3134/4327/2552 3150/4345/2570 3151/4346/2571 +f 3132/4325/2550 3134/4327/2552 3655/4347/2572 +f 3132/4325/2550 3655/4347/2572 3152/4348/2573 +f 3129/4322/2547 3132/4325/2550 3152/4348/2573 +f 3129/4322/2547 3152/4348/2573 3153/4349/2574 +f 3130/4323/2548 3129/4322/2547 3733/4350/2575 +f 3130/4323/2548 3733/4350/2575 3154/4351/2576 +f 3123/4316/2541 3126/4319/2544 3155/4352/2577 +f 3123/4316/2541 3155/4352/2577 3156/4353/2578 +f 3124/4317/2542 3123/4316/2541 3156/4353/2578 +f 3124/4317/2542 3156/4353/2578 3157/4354/2579 +f 3146/4339/2564 3124/4317/2542 3157/4354/2579 +f 3146/4339/2564 3157/4354/2579 3158/4355/2580 +f 3144/4337/2562 3146/4339/2564 3871/4356/2581 +f 3144/4337/2562 3871/4356/2581 3159/4357/2582 +f 3142/4335/2560 3144/4337/2562 3159/4357/2582 +f 3142/4335/2560 3159/4357/2582 3147/4340/2565 +f 3161/1301/168 3170/4358/168 3171/1310/168 +f 3774/1315/170 3176/4359/170 3177/1316/170 +f 3182/4360/2583 3183/4361/2584 3184/4362/2583 +f 3182/4360/2583 3184/4362/2583 3185/4363/2585 +f 3186/4364/2586 3187/4365/2587 3188/4366/2586 +f 3186/4364/2586 3188/4366/2586 3189/4367/2588 +f 3190/1326/174 3192/1328/174 3193/4368/174 +f 3953/1337/177 3929/4369/177 3775/1338/177 +f 3200/4370/2589 3201/4371/2590 3202/4372/2589 +f 3200/4370/2589 3202/4372/2589 3203/4373/2591 +f 3204/4374/2592 3205/4375/2593 3206/4376/2592 +f 3204/4374/2592 3206/4376/2592 3207/4377/2594 +f 3208/4378/2595 3209/4379/2596 3210/4380/2597 +f 3208/4378/2595 3210/4380/2597 3211/4381/2598 +f 3976/4382/2599 3212/4383/2600 3213/4384/2601 +f 3976/4382/2599 3213/4384/2601 3957/4385/2602 +f 3994/4386/2603 3214/4387/2604 3215/4388/2605 +f 3994/4386/2603 3215/4388/2605 3977/4389/2606 +f 3992/4390/2607 3216/4391/2608 3217/4392/2609 +f 3992/4390/2607 3217/4392/2609 3973/4393/2610 +f 3969/4394/2611 3218/4395/2612 3219/4396/2613 +f 3969/4394/2611 3219/4396/2613 3954/4397/2614 +f 3220/4398/2615 3949/4399/2616 3941/4400/2617 +f 3220/4398/2615 3941/4400/2617 3221/4401/2618 +f 3222/1340/178 3224/1342/178 3225/4402/178 +f 3228/1345/179 3230/1347/179 3231/1348/179 +f 3248/1375/187 3249/4403/187 3250/1376/187 +f 3262/4404/2619 3263/4405/2620 3820/4406/2621 +f 3262/4404/2619 3820/4406/2621 3817/4407/2622 +f 3264/4408/2623 3265/4409/2624 3770/4410/2625 +f 3264/4408/2623 3770/4410/2625 3773/4411/2626 +f 3266/4412/2627 3267/4413/2619 3818/4414/2622 +f 3266/4412/2627 3818/4414/2622 3804/4415/2628 +f 3268/4416/2624 3269/4417/2629 3761/4418/2630 +f 3268/4416/2624 3761/4418/2630 3771/4419/2625 +f 3270/4420/2631 3266/4412/2627 3804/4415/2628 +f 3270/4420/2631 3804/4415/2628 3792/4421/2632 +f 3269/4417/2629 3271/4422/2633 3745/4423/2634 +f 3269/4417/2629 3745/4423/2634 3761/4418/2630 +f 3272/4424/2635 3270/4420/2631 3792/4421/2632 +f 3272/4424/2635 3792/4421/2632 3794/4425/2636 +f 3271/4422/2633 3273/4426/2637 3747/4427/2638 +f 3271/4422/2633 3747/4427/2638 3745/4423/2634 +f 3274/4428/2639 3272/4424/2635 3794/4425/2636 +f 3274/4428/2639 3794/4425/2636 3806/4429/2640 +f 3273/4426/2637 3275/4430/2641 3763/4431/2642 +f 3273/4426/2637 3763/4431/2642 3747/4427/2638 +f 3263/4405/2620 3274/4428/2639 3806/4429/2640 +f 3263/4405/2620 3806/4429/2640 3820/4406/2621 +f 3275/4430/2641 3264/4408/2623 3773/4411/2626 +f 3275/4430/2641 3773/4411/2626 3763/4431/2642 +f 3276/4432/2643 3277/4433/2644 3278/4434/2645 +f 3276/4432/2643 3278/4434/2645 3279/4435/2646 +f 3280/4436/2647 3913/4437/2648 3850/4438/2649 +f 3280/4436/2647 3850/4438/2649 3281/4439/2650 +f 3282/4440/2651 3916/4441/2652 3852/4442/2653 +f 3282/4440/2651 3852/4442/2653 3283/4443/2654 +f 3284/4444/2655 3898/4445/2656 3842/4446/2657 +f 3284/4444/2655 3842/4446/2657 3285/4447/2658 +f 3286/4448/2659 3881/4449/2660 3832/4450/2661 +f 3286/4448/2659 3832/4450/2661 3287/4451/2662 +f 3894/4452/2663 3288/4453/2664 3289/4454/2665 +f 3894/4452/2663 3289/4454/2665 3839/4455/2666 +f 3279/4435/2646 3278/4434/2645 3290/4456/2667 +f 3279/4435/2646 3290/4456/2667 3291/4457/2668 +f 3281/4439/2650 3850/4438/2649 3815/4458/2669 +f 3281/4439/2650 3815/4458/2669 3292/4459/2670 +f 3283/4443/2654 3852/4442/2653 3814/4460/2671 +f 3283/4443/2654 3814/4460/2671 3293/4461/2672 +f 3285/4447/2658 3842/4446/2657 3807/4462/2673 +f 3285/4447/2658 3807/4462/2673 3294/4463/2674 +f 3287/4451/2662 3832/4450/2661 3798/4464/2675 +f 3287/4451/2662 3798/4464/2675 3295/4465/2676 +f 3839/4455/2666 3289/4454/2665 3296/4466/2677 +f 3839/4455/2666 3296/4466/2677 3808/4467/2678 +f 3973/4393/2610 3217/4392/2609 3277/4433/2644 +f 3973/4393/2610 3277/4433/2644 3276/4432/2643 +f 3977/4389/2606 3215/4388/2605 3913/4437/2648 +f 3977/4389/2606 3913/4437/2648 3280/4436/2647 +f 3957/4385/2602 3213/4384/2601 3916/4441/2652 +f 3957/4385/2602 3916/4441/2652 3282/4440/2651 +f 3211/4381/2598 3210/4380/2597 3898/4445/2656 +f 3211/4381/2598 3898/4445/2656 3284/4444/2655 +f 3221/4401/2618 3941/4400/2617 3881/4449/2660 +f 3221/4401/2618 3881/4449/2660 3286/4448/2659 +f 3954/4397/2614 3219/4396/2613 3288/4453/2664 +f 3954/4397/2614 3288/4453/2664 3894/4452/2663 +f 3297/4468/2679 3298/4469/2680 3299/4470/2681 +f 3297/4468/2679 3299/4470/2681 3300/4471/2682 +f 3301/4472/2683 3302/4473/2684 3787/4474/2685 +f 3301/4472/2683 3787/4474/2685 3781/4475/2686 +f 3303/4476/2687 3304/4477/2688 3302/4473/2684 +f 3303/4476/2687 3302/4473/2684 3301/4472/2683 +f 3305/4478/2689 3306/4479/2690 3298/4469/2680 +f 3305/4478/2689 3298/4469/2680 3297/4468/2679 +f 3904/4480/2691 3880/4481/2692 3304/4477/2688 +f 3904/4480/2691 3304/4477/2688 3303/4476/2687 +f 3307/4482/2693 3308/4483/2694 3306/4479/2690 +f 3307/4482/2693 3306/4479/2690 3305/4478/2689 +f 3880/4481/2692 3307/4482/2693 3305/4478/2689 +f 3880/4481/2692 3305/4478/2689 3304/4477/2688 +f 3302/4473/2684 3304/4477/2688 3305/4478/2689 +f 3302/4473/2684 3305/4478/2689 3297/4468/2679 +f 3787/4474/2685 3302/4473/2684 3297/4468/2679 +f 3787/4474/2685 3297/4468/2679 3300/4471/2682 +f 3309/4484/2695 3310/4485/2696 3311/4486/2697 +f 3309/4484/2695 3311/4486/2697 3312/4487/2698 +f 3826/4488/2699 3782/4489/2700 3313/4490/2701 +f 3826/4488/2699 3313/4490/2701 3314/4491/2702 +f 3854/4492/2703 3826/4488/2699 3314/4491/2702 +f 3854/4492/2703 3314/4491/2702 3315/4493/2704 +f 3316/4494/2705 3309/4484/2695 3312/4487/2698 +f 3316/4494/2705 3312/4487/2698 3317/4495/2706 +f 3905/4496/2707 3854/4492/2703 3315/4493/2704 +f 3905/4496/2707 3315/4493/2704 3318/4497/2708 +f 3319/4498/2709 3316/4494/2705 3317/4495/2706 +f 3319/4498/2709 3317/4495/2706 3320/4499/2710 +f 3318/4497/2708 3315/4493/2704 3316/4494/2705 +f 3318/4497/2708 3316/4494/2705 3319/4498/2709 +f 3314/4491/2702 3309/4484/2695 3316/4494/2705 +f 3314/4491/2702 3316/4494/2705 3315/4493/2704 +f 3313/4490/2701 3310/4485/2696 3309/4484/2695 +f 3313/4490/2701 3309/4484/2695 3314/4491/2702 +f 3321/4500/2711 3322/4501/2712 3783/4502/2713 +f 3321/4500/2711 3783/4502/2713 3769/4503/2711 +f 3323/4504/2714 3324/4505/2715 3813/4506/2715 +f 3323/4504/2714 3813/4506/2715 3845/4507/2716 +f 314/103/9 3323/4504/2714 3845/4507/2716 +f 314/103/9 3845/4507/2716 315/104/9 +f 3322/4501/2712 310/1707/420 312/1709/420 +f 3322/4501/2712 312/1709/420 3783/4502/2713 +f 3325/4508/2717 3326/4509/2718 3327/4510/2719 +f 3325/4508/2717 3327/4510/2719 3328/4511/2720 +f 3326/4509/2718 3329/4512/2721 3330/4513/2722 +f 3326/4509/2718 3330/4513/2722 3327/4510/2719 +f 3329/4512/2721 3331/4514/2723 3332/4515/2724 +f 3329/4512/2721 3332/4515/2724 3330/4513/2722 +f 3331/4514/2723 3333/4516/2725 3334/4517/2726 +f 3331/4514/2723 3334/4517/2726 3332/4515/2724 +f 3333/4516/2725 3335/4518/2727 3336/4519/2728 +f 3333/4516/2725 3336/4519/2728 3334/4517/2726 +f 3335/4518/2727 3337/4520/2729 3338/4521/2730 +f 3335/4518/2727 3338/4521/2730 3336/4519/2728 +f 3337/4520/2729 3339/4522/2731 3340/4523/2732 +f 3337/4520/2729 3340/4523/2732 3338/4521/2730 +f 3339/4522/2731 3341/4524/2733 3342/4525/2734 +f 3339/4522/2731 3342/4525/2734 3340/4523/2732 +f 3341/4524/2733 3343/4526/2735 3344/4527/2736 +f 3341/4524/2733 3344/4527/2736 3342/4525/2734 +f 3345/4528/2735 3325/4508/2717 3328/4511/2720 +f 3345/4528/2735 3328/4511/2720 3346/4529/2736 +f 3348/1399/193 3356/1407/193 3349/1400/193 +f 3357/4530/2737 3358/4531/2738 3359/4532/2739 +f 3357/4530/2737 3359/4532/2739 3360/4533/2740 +f 3358/4531/2738 3361/4534/2741 3362/4535/2742 +f 3358/4531/2738 3362/4535/2742 3359/4532/2739 +f 3361/4534/2741 3363/4536/2743 3364/4537/2744 +f 3361/4534/2741 3364/4537/2744 3362/4535/2742 +f 3363/4536/2743 3365/4538/2745 3366/4539/2746 +f 3363/4536/2743 3366/4539/2746 3364/4537/2744 +f 3365/4538/2745 3367/4540/2747 3368/4541/2748 +f 3365/4538/2745 3368/4541/2748 3366/4539/2746 +f 3367/4540/2747 3369/4542/2749 3370/4543/2750 +f 3367/4540/2747 3370/4543/2750 3368/4541/2748 +f 3369/4542/2749 3371/4544/2751 3372/4545/2752 +f 3369/4542/2749 3372/4545/2752 3370/4543/2750 +f 3371/4544/2751 3373/4546/2753 3374/4547/2754 +f 3371/4544/2751 3374/4547/2754 3372/4545/2752 +f 3373/4546/2753 3375/4548/2755 3376/4549/2756 +f 3373/4546/2753 3376/4549/2756 3374/4547/2754 +f 3377/4550/2757 3357/4530/2737 3360/4533/2740 +f 3377/4550/2757 3360/4533/2740 3378/4551/2756 +f 3358/4531/2738 3357/4530/2737 3379/4552/2758 +f 3358/4531/2738 3379/4552/2758 3380/4553/2759 +f 3361/4534/2741 3358/4531/2738 3380/4553/2759 +f 3361/4534/2741 3380/4553/2759 3381/4554/2760 +f 3363/4536/2743 3361/4534/2741 3381/4554/2760 +f 3363/4536/2743 3381/4554/2760 3382/4555/2761 +f 3365/4538/2745 3363/4536/2743 3382/4555/2761 +f 3365/4538/2745 3382/4555/2761 3383/4556/2762 +f 3367/4540/2747 3365/4538/2745 3383/4556/2762 +f 3367/4540/2747 3383/4556/2762 3384/4557/2763 +f 3369/4542/2749 3367/4540/2747 3384/4557/2763 +f 3369/4542/2749 3384/4557/2763 3385/4558/2764 +f 3371/4544/2751 3369/4542/2749 3385/4558/2764 +f 3371/4544/2751 3385/4558/2764 3386/4559/2765 +f 3373/4546/2753 3371/4544/2751 3386/4559/2765 +f 3373/4546/2753 3386/4559/2765 3387/4560/2766 +f 3375/4548/2755 3373/4546/2753 3387/4560/2766 +f 3375/4548/2755 3387/4560/2766 3388/4561/2767 +f 3357/4530/2737 3377/4550/2757 3389/4562/2767 +f 3357/4530/2737 3389/4562/2767 3379/4552/2758 +f 3380/4553/2759 3379/4552/2758 3390/4563/2768 +f 3380/4553/2759 3390/4563/2768 3391/4564/2769 +f 3381/4554/2760 3380/4553/2759 3391/4564/2769 +f 3381/4554/2760 3391/4564/2769 3392/4565/2770 +f 3382/4555/2761 3381/4554/2760 3392/4565/2770 +f 3382/4555/2761 3392/4565/2770 3393/4566/2771 +f 3383/4556/2762 3382/4555/2761 3393/4566/2771 +f 3383/4556/2762 3393/4566/2771 3394/4567/2772 +f 3384/4557/2763 3383/4556/2762 3394/4567/2772 +f 3384/4557/2763 3394/4567/2772 3395/4568/2773 +f 3385/4558/2764 3384/4557/2763 3395/4568/2773 +f 3385/4558/2764 3395/4568/2773 3396/4569/2774 +f 3386/4559/2765 3385/4558/2764 3396/4569/2774 +f 3386/4559/2765 3396/4569/2774 3397/4570/2775 +f 3387/4560/2766 3386/4559/2765 3397/4570/2775 +f 3387/4560/2766 3397/4570/2775 3398/4571/2776 +f 3388/4561/2767 3387/4560/2766 3398/4571/2776 +f 3388/4561/2767 3398/4571/2776 3399/4572/2777 +f 3379/4552/2758 3389/4562/2767 3400/4573/2777 +f 3379/4552/2758 3400/4573/2777 3390/4563/2768 +f 4171/4574/2778 3405/4575/2779 3406/4576/2780 +f 4171/4574/2778 3406/4576/2780 4165/4577/2781 +f 3407/4578/2782 3408/4579/2783 3409/4580/2782 +f 3407/4578/2782 3409/4580/2782 3410/4581/2784 +f 4156/4582/2785 3413/4583/2786 3414/4584/2785 +f 4156/4582/2785 3414/4584/2785 4131/4585/2787 +f 4161/4586/2788 4166/4587/2789 4160/4588/2788 +f 4161/4586/2788 4160/4588/2788 4146/4589/2790 +f 3421/1422/198 3422/4590/198 3423/1423/198 +f 3427/4591/2791 3428/4592/2792 3429/4593/2793 +f 3427/4591/2791 3429/4593/2793 3430/4594/2794 +f 3430/4594/2794 3429/4593/2793 3431/4595/2795 +f 3430/4594/2794 3431/4595/2795 3432/4596/2796 +f 3432/4596/2796 3431/4595/2795 3433/4597/2797 +f 3432/4596/2796 3433/4597/2797 3434/4598/2798 +f 3434/4598/2798 3433/4597/2797 3435/4599/2799 +f 3434/4598/2798 3435/4599/2799 3436/4600/2800 +f 3436/4600/2800 3435/4599/2799 3437/4601/2801 +f 3436/4600/2800 3437/4601/2801 3438/4602/2802 +f 3438/4602/2802 3437/4601/2801 3439/4603/2803 +f 3438/4602/2802 3439/4603/2803 3440/4604/2804 +f 3440/4604/2804 3439/4603/2803 3441/4605/2805 +f 3440/4604/2804 3441/4605/2805 3442/4606/2806 +f 3442/4606/2806 3441/4605/2805 3443/4607/2807 +f 3442/4606/2806 3443/4607/2807 3444/4608/2808 +f 3444/4608/2808 3443/4607/2807 3445/4609/2809 +f 3444/4608/2808 3445/4609/2809 3446/4610/2810 +f 3447/4611/2811 3448/4612/2812 3428/4592/2792 +f 3447/4611/2811 3428/4592/2792 3427/4591/2791 +f 3455/1433/200 3456/4613/200 3454/1432/200 +f 3459/4614/2813 3460/4615/2814 3461/4616/2815 +f 3459/4614/2813 3461/4616/2815 3462/4617/2816 +f 3462/4617/2816 3461/4616/2815 3463/4618/2817 +f 3462/4617/2816 3463/4618/2817 3464/4619/2818 +f 3464/4619/2818 3463/4618/2817 3465/4620/2819 +f 3464/4619/2818 3465/4620/2819 3466/4621/2820 +f 3466/4621/2820 3465/4620/2819 3467/4622/2821 +f 3466/4621/2820 3467/4622/2821 3468/4623/2822 +f 3468/4623/2822 3467/4622/2821 3469/4624/2823 +f 3468/4623/2822 3469/4624/2823 3470/4625/2824 +f 3470/4625/2824 3469/4624/2823 3471/4626/2825 +f 3470/4625/2824 3471/4626/2825 3472/4627/2826 +f 3472/4627/2826 3471/4626/2825 3473/4628/2827 +f 3472/4627/2826 3473/4628/2827 3474/4629/2828 +f 3474/4629/2828 3473/4628/2827 3475/4630/2829 +f 3474/4629/2828 3475/4630/2829 3476/4631/2830 +f 3476/4631/2830 3475/4630/2829 3477/4632/2831 +f 3476/4631/2830 3477/4632/2831 3478/4633/2832 +f 3479/4634/2833 3480/4635/2831 3460/4615/2814 +f 3479/4634/2833 3460/4615/2814 3459/4614/2813 +f 3462/4617/2816 3481/4636/2834 3482/4637/2835 +f 3462/4617/2816 3482/4637/2835 3459/4614/2813 +f 3464/4619/2818 3483/4638/2836 3481/4636/2834 +f 3464/4619/2818 3481/4636/2834 3462/4617/2816 +f 3466/4621/2820 3484/4639/2837 3483/4638/2836 +f 3466/4621/2820 3483/4638/2836 3464/4619/2818 +f 3468/4623/2822 3485/4640/2838 3484/4639/2837 +f 3468/4623/2822 3484/4639/2837 3466/4621/2820 +f 3470/4625/2824 3486/4641/2839 3485/4640/2838 +f 3470/4625/2824 3485/4640/2838 3468/4623/2822 +f 3472/4627/2826 3487/4642/2840 3486/4641/2839 +f 3472/4627/2826 3486/4641/2839 3470/4625/2824 +f 3474/4629/2828 3488/4643/2841 3487/4642/2840 +f 3474/4629/2828 3487/4642/2840 3472/4627/2826 +f 3476/4631/2830 3489/4644/2842 3488/4643/2841 +f 3476/4631/2830 3488/4643/2841 3474/4629/2828 +f 3478/4633/2832 3490/4645/2843 3489/4644/2842 +f 3478/4633/2832 3489/4644/2842 3476/4631/2830 +f 3459/4614/2813 3482/4637/2835 3491/4646/2843 +f 3459/4614/2813 3491/4646/2843 3479/4634/2833 +f 3481/4636/2834 3492/4647/2844 3493/4648/2845 +f 3481/4636/2834 3493/4648/2845 3482/4637/2835 +f 3483/4638/2836 3494/4649/2846 3492/4647/2844 +f 3483/4638/2836 3492/4647/2844 3481/4636/2834 +f 3484/4639/2837 3495/4650/2847 3494/4649/2846 +f 3484/4639/2837 3494/4649/2846 3483/4638/2836 +f 3485/4640/2838 3496/4651/2848 3495/4650/2847 +f 3485/4640/2838 3495/4650/2847 3484/4639/2837 +f 3486/4641/2839 3497/4652/2849 3496/4651/2848 +f 3486/4641/2839 3496/4651/2848 3485/4640/2838 +f 3487/4642/2840 3498/4653/2850 3497/4652/2849 +f 3487/4642/2840 3497/4652/2849 3486/4641/2839 +f 3488/4643/2841 3499/4654/2851 3498/4653/2850 +f 3488/4643/2841 3498/4653/2850 3487/4642/2840 +f 3489/4644/2842 3500/4655/2852 3499/4654/2851 +f 3489/4644/2842 3499/4654/2851 3488/4643/2841 +f 3490/4645/2843 3501/4656/2853 3500/4655/2852 +f 3490/4645/2843 3500/4655/2852 3489/4644/2842 +f 3482/4637/2835 3493/4648/2845 3502/4657/2854 +f 3482/4637/2835 3502/4657/2854 3491/4646/2843 +f 4397/4658/2855 4399/4659/2856 3507/4660/2857 +f 4397/4658/2855 3507/4660/2857 3508/4661/2858 +f 3509/4662/2859 3510/4663/2860 3511/4664/2859 +f 3509/4662/2859 3511/4664/2859 3512/4665/2861 +f 4404/4666/2862 4417/4667/2863 3515/4668/2862 +f 4404/4666/2862 3515/4668/2862 3516/4669/2864 +f 4401/4670/2865 4407/4671/2866 4402/4672/2865 +f 4401/4670/2865 4402/4672/2865 4398/4673/2867 +f 3523/1450/205 3524/1455/205 3525/1451/205 +f 3529/1456/2868 3530/1459/2868 3531/1457/2868 +f 3533/1460/207 316/1710/421 318/1712/423 +f 3533/1460/207 318/1712/423 3534/1461/2868 +f 3540/1467/208 320/1468/425 319/1713/424 +f 3541/1469/210 3543/1471/210 3544/4674/210 +f 3545/1472/210 322/1715/427 324/1717/429 +f 3545/1472/210 324/1717/429 3546/1473/210 +f 3547/1474/211 3548/4675/211 3549/1475/211 +f 3550/1476/211 3549/1475/211 3551/1477/211 +f 3552/1478/211 326/1479/431 325/1718/430 diff --git a/resources/viking_room.png b/resources/viking_room.png new file mode 100644 index 00000000..6a879ea3 Binary files /dev/null and b/resources/viking_room.png differ diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..84d6b942 --- /dev/null +++ b/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 3 +requires-python = "==3.13.*" + +[[package]] +name = "vulkantutorial" +version = "0.1.0" +source = { virtual = "." }