├── .gitignore ├── LICENSE.CC-BY-SA-40 ├── LICENSE.CC0 ├── README.md ├── build_ebook.py ├── code ├── .gitignore ├── 00_base_code.cpp ├── 01_instance_creation.cpp ├── 02_validation_layers.cpp ├── 03_physical_device_selection.cpp ├── 04_logical_device.cpp ├── 05_window_surface.cpp ├── 06_swap_chain_creation.cpp ├── 07_image_views.cpp ├── 08_graphics_pipeline.cpp ├── 09_shader_base.frag ├── 09_shader_base.vert ├── 09_shader_modules.cpp ├── 10_fixed_functions.cpp ├── 11_render_passes.cpp ├── 12_graphics_pipeline_complete.cpp ├── 13_framebuffers.cpp ├── 14_command_buffers.cpp ├── 15_hello_triangle.cpp ├── 16_frames_in_flight.cpp ├── 17_swap_chain_recreation.cpp ├── 18_shader_vertexbuffer.frag ├── 18_shader_vertexbuffer.vert ├── 18_vertex_input.cpp ├── 19_vertex_buffer.cpp ├── 20_staging_buffer.cpp ├── 21_index_buffer.cpp ├── 22_descriptor_set_layout.cpp ├── 22_shader_ubo.frag ├── 22_shader_ubo.vert ├── 23_descriptor_sets.cpp ├── 24_texture_image.cpp ├── 25_sampler.cpp ├── 26_shader_textures.frag ├── 26_shader_textures.vert ├── 26_texture_mapping.cpp ├── 27_depth_buffering.cpp ├── 27_shader_depth.frag ├── 27_shader_depth.vert ├── 28_model_loading.cpp ├── 29_mipmapping.cpp ├── 30_multisampling.cpp ├── 31_compute_shader.cpp ├── 31_shader_compute.comp ├── 31_shader_compute.frag ├── 31_shader_compute.vert ├── CMakeLists.txt └── incremental_patch.sh ├── config.json ├── ebook ├── cover.kra ├── cover.png └── listings-setup.tex ├── en ├── 00_Introduction.md ├── 01_Overview.md ├── 02_Development_environment.md ├── 03_Drawing_a_triangle │ ├── 00_Setup │ │ ├── 00_Base_code.md │ │ ├── 01_Instance.md │ │ ├── 02_Validation_layers.md │ │ ├── 03_Physical_devices_and_queue_families.md │ │ └── 04_Logical_device_and_queues.md │ ├── 01_Presentation │ │ ├── 00_Window_surface.md │ │ ├── 01_Swap_chain.md │ │ └── 02_Image_views.md │ ├── 02_Graphics_pipeline_basics │ │ ├── 00_Introduction.md │ │ ├── 01_Shader_modules.md │ │ ├── 02_Fixed_functions.md │ │ ├── 03_Render_passes.md │ │ └── 04_Conclusion.md │ ├── 03_Drawing │ │ ├── 00_Framebuffers.md │ │ ├── 01_Command_buffers.md │ │ ├── 02_Rendering_and_presentation.md │ │ └── 03_Frames_in_flight.md │ └── 04_Swap_chain_recreation.md ├── 04_Vertex_buffers │ ├── 00_Vertex_input_description.md │ ├── 01_Vertex_buffer_creation.md │ ├── 02_Staging_buffer.md │ └── 03_Index_buffer.md ├── 05_Uniform_buffers │ ├── 00_Descriptor_set_layout_and_buffer.md │ └── 01_Descriptor_pool_and_sets.md ├── 06_Texture_mapping │ ├── 00_Images.md │ ├── 01_Image_view_and_sampler.md │ └── 02_Combined_image_sampler.md ├── 07_Depth_buffering.md ├── 08_Loading_models.md ├── 09_Generating_Mipmaps.md ├── 10_Multisampling.md ├── 11_Compute_Shader.md ├── 90_FAQ.md └── 95_Privacy_policy.md ├── fr ├── 00_Introduction.md ├── 01_Vue_d'ensemble.md ├── 02_Environnement_de_développement.md ├── 03_Dessiner_un_triangle │ ├── 00_Mise_en_place │ │ ├── 00_Code_de_base.md │ │ ├── 01_Instance.md │ │ ├── 02_Validation_layers.md │ │ ├── 03_Physical_devices_et_queue_families.md │ │ └── 04_Logical_device_et_queues.md │ ├── 01_Présentation │ │ ├── 00_Window_surface.md │ │ ├── 01_Swap_chain.md │ │ └── 02_Image_views.md │ ├── 02_Pipeline_graphique_basique │ │ ├── 00_Introduction.md │ │ ├── 01_Modules_shaders.md │ │ ├── 02_Fonctions_fixées.md │ │ ├── 03_Render_pass.md │ │ └── 04_Conclusion.md │ ├── 03_Effectuer_le_rendu │ │ ├── 00_Framebuffers.md │ │ ├── 01_Command_buffers.md │ │ └── 02_Rendu_et_présentation.md │ └── 04_Recréation_de_la_swap_chain.md ├── 04_Vertex_buffers │ ├── 00_Description_des_entrées_des_sommets.md │ ├── 01_Création_de_vertex_buffers.md │ ├── 02_Buffer_intermédiaire.md │ └── 03_Index_buffer.md ├── 05_Uniform_buffers │ ├── 00_Descriptor_layout_et_buffer.md │ └── 01_Descriptor_pool_et_sets.md ├── 06_Texture_mapping │ ├── 00_Images.md │ ├── 01_Vue_sur_image_et_sampler.md │ └── 02_Sampler_d'image_combiné.md ├── 07_Buffer_de_profondeur.md ├── 08_Charger_des_modèles.md ├── 09_Générer_des_mipmaps.md ├── 10_Multisampling.md ├── 90_FAQ.md └── 95_Politique_de_confidentialité.md ├── images ├── aliasing.png ├── anisotropic_filtering.png ├── antialiasing.png ├── compute_shader_particles.png ├── compute_space.svg ├── compute_ssbo_read_write.svg ├── cube_demo.png ├── cube_demo_mac.png ├── cube_demo_nowindow.png ├── depth_correct.png ├── depth_issues.png ├── drawing_model.png ├── extra_square.svg ├── favicon.png ├── glfw_directory.png ├── highmipmaps.png ├── include_dirs_stb.png ├── include_dirs_tinyobjloader.png ├── indexed_rectangle.png ├── inverted_texture_coordinates.png ├── library_directory.png ├── mipmaps.png ├── mipmaps_comparison.png ├── mipmaps_example.jpg ├── multisampling.png ├── multisampling_comparison.png ├── multisampling_comparison2.png ├── normalized_device_coordinates.svg ├── sample_shading.png ├── select_develop_branch.png ├── semaphore_in_use.png ├── spinning_quad.png ├── steam_layers_env.png ├── swap_chain_validation_layer.png ├── texcoord_visualization.png ├── texture.jpg ├── texture_addressing.png ├── texture_filtering.png ├── texture_on_square.png ├── texture_on_square_colorized.png ├── texture_on_square_repeated.png ├── triangle.png ├── triangle_coordinates.svg ├── triangle_coordinates_colors.png ├── triangle_white.png ├── validation_layer_anisotropy.png ├── validation_layer_test.png ├── vertex_vs_index.svg ├── viewports_scissors.png ├── vs_all_configs.png ├── vs_application_settings.png ├── vs_build_mode.png ├── vs_cpp17.png ├── vs_cpp_general.png ├── vs_dependencies.png ├── vs_export_template.png ├── vs_include_dirs.png ├── vs_link_dirs.png ├── vs_link_input.png ├── vs_link_settings.png ├── vs_new_cpp_project.png ├── vs_new_item.png ├── vs_new_source_file.png ├── vs_open_project_properties.png ├── vs_template.png ├── vs_test_window.png ├── vulkan_pipeline_block_diagram.png ├── vulkan_sdk_download_buttons.png ├── vulkan_simplified_pipeline.svg ├── xcode_frameworks.png ├── xcode_new_project.png ├── xcode_new_project_2.png ├── xcode_output.png ├── xcode_paths.png └── xcode_variables.png └── resources ├── viking_room.obj └── viking_room.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | ads.txt 3 | 4 | **/_out/* 5 | **/.vscode/* 6 | .DS_Store 7 | build_ebook.log 8 | temp_ebook.md 9 | ebook/*.pdf 10 | ebook/*.epub -------------------------------------------------------------------------------- /LICENSE.CC0: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Vulkan tutorial 2 | =============== 3 | 4 | This repository hosts the contents of [vulkan-tutorial.com](https://vulkan-tutorial.com). 5 | The website itself is based on [daux.io](https://github.com/dauxio/daux.io), 6 | which supports [GitHub flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). 7 | 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). 8 | 9 | Use issues and pull requests to provide feedback related to the website. If you 10 | have a problem with your code, then use the comments section in the related 11 | chapter to ask a question. Please provide your operating system, graphics card, 12 | driver version, source code, expected behaviour and actual behaviour. 13 | 14 | E-book 15 | ------ 16 | 17 | This guide is now available in e-book formats as well: 18 | 19 | * EPUB ([English](https://vulkan-tutorial.com/resources/vulkan_tutorial_en.epub), [French](https://vulkan-tutorial.com/resources/vulkan_tutorial_fr.epub)) 20 | * PDF ([English](https://vulkan-tutorial.com/resources/vulkan_tutorial_en.pdf), [French](https://vulkan-tutorial.com/resources/vulkan_tutorial_fr.pdf)) 21 | 22 | The e-book can be built from the existing content by running: 23 | 24 | python3 build_ebook.py 25 | 26 | This script depends on the following utilities being available on the path: 27 | 28 | * `inkscape`: SVG to PNG conversion (tested with version 1.0.2) 29 | * `pandoc`: Building a PDF and EPUB from the Markdown code (tested with version 2.13) 30 | 31 | You also need to install a LaTeX distribution for PDF generation. 32 | 33 | Changing code across chapters 34 | ----------------------------- 35 | 36 | It is sometimes necessary to change code that is reused across many chapters, 37 | for example a function like `createBuffer`. If you make such a change, then you 38 | should update the code files using the following steps: 39 | 40 | * Update any chapters that reference the modified code. 41 | * Make a copy of the first file that uses it and modify the code there, e.g. 42 | `base_code_fixed.cpp`. 43 | * Create a patch using 44 | `diff -Naur base_code.cpp base_code_fixed.cpp > patch.txt`. 45 | * Apply the patch to the specified code file and all files in later chapters 46 | using the `incremental_patch.sh` script. Run it like this: 47 | `./incremental_patch.sh base_code.cpp patch.txt`. 48 | * Clean up the `base_code_fixed.cpp` and `patch.txt` files. 49 | * Commit. 50 | 51 | Rendering the tutorial 52 | ----------------------------- 53 | 54 | To render the tutorial (i.e. convert the markdown to html), you have two options: 55 | 56 | 1. Serve rendered files on the fly using a web server that has php installed 57 | 2. Generate static html files that you can view locally or put on a server 58 | 59 | For either of these options, you'll need php and a patch'ed daux. 60 | 61 | ### PHP 62 | 63 | 1. Make sure [PHP](http://php.net/downloads.php) is installed (Daux is written 64 | in PHP) 65 | 1. Both the `php_mbstring` and `php_openssl` extensions need to be enabled 66 | 2. The `phar.readonly` setting needs to be set to `Off` (to be able to 67 | rebuild Daux) 68 | 2. Make sure [Composer](https://getcomposer.org/) is installed, a php dependency 69 | manager that Daux uses 70 | 71 | ### Clone, patch, and rebuild daux 72 | 73 | 1. Clone [daux](https://github.com/dauxio/daux.io) 74 | * `git clone https://github.com/dauxio/daux.io.git` 75 | 2. Make a new branch at the older revision that the VulkanTutorial patch is 76 | against: 77 | * `git checkout d45ccff -b vtpatch` 78 | * Making a new branch isn't strictly necessary, as you could reset `master`, 79 | but this keeps master intact. 80 | 3. Copy over the `daux.patch` file into the daux.io directory, make sure line 81 | endings are UNIX style (in case you're using Windows), and apply the patch. 82 | It should apply cleanly. 83 | * `git am daux.patch` 84 | 4. Run composer in the daux.io directory so that it downloads the dependencies 85 | Daux needs in order to be built 86 | * `composer install` 87 | 5. Rebuild Daux 88 | * `php bin/compile` (this can take a while) 89 | * A newly made `daux.phar` will now be in your base directory 90 | 91 | ### Using Daux to serve rendered files on the fly 92 | 93 | Once you've completed the above, follow the instructions on the daux site 94 | for how to [run daux using a web server](https://github.com/dauxio/daux.io/blob/master/README.md#running-remotely). 95 | 96 | As a simple option considering you have php installed, you can also use php's 97 | built in development web server if you just need to locally see what things 98 | look like: 99 | 100 | 1. In the `daux.io` directory, edit `global.json` so that the `docs_directory` 101 | option points at your VulkanTutorial directory 102 | * `"docs_directory": "../VulkanTutorial",` 103 | 2. In the `daux.io` directory, run 104 | * ` php -S localhost:8080 index.php` 105 | 3. Type `localhost:8080` in your web browser URL bar and hit enter. You should 106 | now see the VulkanTutorial front page. 107 | 108 | ### Using Daux to statically generate html files 109 | 110 | Before we generate the static files, we need to tweak daux and the tutorial 111 | setup to prevent it from trying to load a few outside resources (which will 112 | stall your browser when trying to load the otherwise static page) 113 | 114 | 1. In the `VulkanTutorial` directory, edit `config.json` and remove the 115 | `google_analytics` line so daux doesn't try to load that. 116 | 2. In the `daux.io` directory, edit `themes/daux/config.json` and remove the 117 | `font` line so that daux doesn't try to load an external font. 118 | 3. Rebuild daux according to the earlier instructions so it picks up the theme 119 | changes. 120 | 121 | We're working on improvements so in the future the above steps won't be 122 | necessary. 123 | 124 | Now with the above done, we can generate the static files. Asuming the daux.io 125 | and VulkanTutorial directories are next to each other, go into the `daux.io` 126 | directory and run a command similar to: 127 | `php generate -s ../VulkanTutorial -d ../VulkanTutorial/out`. 128 | 129 | `-s` tells it where to find the documentation, while `-d` tells it where to put 130 | the generated files. 131 | 132 | Note: if you want to generate the docs again, delete the `out` directory first 133 | or daux will make a new `out` directory within the existing `out` directory. 134 | 135 | License 136 | ------- 137 | 138 | The contents of this repository are licensed as [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), 139 | unless stated otherwise. By contributing to this repository, you agree to license 140 | your contributions to the public under that same license. 141 | 142 | The code listings in the `code` directory are licensed as [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/). 143 | By contributing to that directory, you agree to license your contributions to 144 | the public under that same public domain-like license. 145 | -------------------------------------------------------------------------------- /build_ebook.py: -------------------------------------------------------------------------------- 1 | """Generate EPUB and PDF ebooks from sources.""" 2 | 3 | from datetime import datetime 4 | import json 5 | import logging 6 | from pathlib import Path 7 | import re 8 | from tempfile import TemporaryDirectory 9 | import subprocess 10 | from dataclasses import dataclass 11 | from subprocess import CalledProcessError 12 | from re import Match 13 | import shutil 14 | 15 | logging.basicConfig( 16 | format="%(asctime)s %(levelname)-8s %(message)s", 17 | level=logging.INFO, 18 | datefmt="%Y-%m-%d %H:%M:%S", 19 | ) 20 | 21 | 22 | def convert_images(images_dir: Path, converted_image_dir: Path) -> None: 23 | """Convert all SVG images to PNGs.""" 24 | 25 | if not converted_image_dir.exists(): 26 | converted_image_dir.mkdir() 27 | 28 | for source_file in images_dir.glob("*"): 29 | if source_file.suffix == ".svg": 30 | dest_file = converted_image_dir / source_file.with_suffix(".png").name 31 | 32 | try: 33 | subprocess.check_output( 34 | [ 35 | "inkscape", 36 | f"--export-filename={dest_file.as_posix()}", 37 | source_file.as_posix(), 38 | ], 39 | stderr=subprocess.STDOUT, 40 | ) 41 | except FileNotFoundError: 42 | raise RuntimeError( 43 | f"failed to convert {source_file.name} to {dest_file.name}: " 44 | "inkscape not installed" 45 | ) 46 | except CalledProcessError as e: 47 | raise RuntimeError( 48 | f"failed to convert {source_file.name} to {dest_file.name}: " 49 | f"inkscape failed: {e.output.decode()}" 50 | ) 51 | else: 52 | shutil.copy(source_file, converted_image_dir / source_file.name) 53 | 54 | return converted_image_dir 55 | 56 | 57 | @dataclass 58 | class MarkdownChapter: 59 | title: str 60 | depth: int 61 | contents: str 62 | 63 | 64 | def find_markdown_chapters(markdown_dir: Path) -> list[Path]: 65 | """Find all Markdown files and interpret them as chapters.""" 66 | 67 | markdown_entries = list(markdown_dir.rglob("*")) 68 | markdown_entries.sort() 69 | 70 | markdown_chapters = [] 71 | 72 | for markdown_path in markdown_entries: 73 | # Skip privacy policy (regardless of language) 74 | if markdown_path.name.startswith("95_"): 75 | continue 76 | 77 | title = markdown_path.stem.partition("_")[-1].replace("_", " ") 78 | depth = len(markdown_path.relative_to(markdown_dir).parts) - 1 79 | 80 | markdown_chapters.append( 81 | MarkdownChapter( 82 | title=title, 83 | depth=depth, 84 | contents=markdown_path.read_text() if markdown_path.is_file() else "", 85 | ) 86 | ) 87 | 88 | return markdown_chapters 89 | 90 | 91 | def generate_markdown_preface() -> str: 92 | current_date = datetime.now().strftime("%B %Y") 93 | 94 | return "\n".join( 95 | [ 96 | "% Vulkan Tutorial", 97 | "% Alexander Overvoorde", 98 | f"% {current_date}", 99 | ] 100 | ) 101 | 102 | 103 | def generate_markdown_chapter( 104 | chapter: MarkdownChapter, converted_image_dir: Path 105 | ) -> str: 106 | contents = f"# {chapter.title}\n\n{chapter.contents}" 107 | 108 | # Adjust titles based on depth of chapter itself 109 | if chapter.depth > 0: 110 | 111 | def adjust_title_depth(match: Match) -> str: 112 | return ("#" * chapter.depth) + match.group(0) 113 | 114 | contents = re.sub(r"#+ ", adjust_title_depth, contents) 115 | 116 | # Fix image links 117 | contents = contents.replace("/images/", f"{converted_image_dir.as_posix()}/") 118 | contents = contents.replace(".svg", ".png") 119 | 120 | # Fix remaining relative links 121 | contents = contents.replace("(/code", "(https://vulkan-tutorial.com/code") 122 | contents = contents.replace("(/resources", "(https://vulkan-tutorial.com/resources") 123 | 124 | # Fix chapter references 125 | def fix_chapter_reference(match: Match) -> str: 126 | target = match.group(1).lower().replace("_", "-").split("/")[-1] 127 | return f"](#{target})" 128 | 129 | contents = re.sub(r"\]\(!([^)]+)\)", fix_chapter_reference, contents) 130 | 131 | return contents 132 | 133 | 134 | def compile_full_markdown( 135 | markdown_dir: Path, markdown_file: Path, converted_image_dir: Path 136 | ) -> Path: 137 | """Combine Markdown source files into one large file.""" 138 | 139 | markdown_fragments = [generate_markdown_preface()] 140 | 141 | for chapter in find_markdown_chapters(markdown_dir): 142 | markdown_fragments.append( 143 | generate_markdown_chapter(chapter, converted_image_dir) 144 | ) 145 | 146 | markdown_file.write_text("\n\n".join(markdown_fragments)) 147 | 148 | return markdown_file 149 | 150 | 151 | def build_pdf(markdown_file: Path, pdf_file: Path) -> Path: 152 | """Build combined Markdown file into a PDF.""" 153 | 154 | try: 155 | subprocess.check_output(["xelatex", "--version"]) 156 | except FileNotFoundError: 157 | raise RuntimeError(f"failed to build {pdf_file}: xelatex not installed") 158 | 159 | try: 160 | subprocess.check_output( 161 | [ 162 | "pandoc", 163 | markdown_file.as_posix(), 164 | "-V", 165 | "documentclass=report", 166 | "-t", 167 | "latex", 168 | "-s", 169 | "--toc", 170 | "--listings", 171 | "-H", 172 | "ebook/listings-setup.tex", 173 | "-o", 174 | pdf_file.as_posix(), 175 | "--pdf-engine=xelatex", 176 | ] 177 | ) 178 | except CalledProcessError as e: 179 | raise RuntimeError( 180 | f"failed to build {pdf_file}: pandoc failed: {e.output.decode()}" 181 | ) 182 | 183 | return pdf_file 184 | 185 | 186 | def build_epub(markdown_file: Path, epub_file: Path) -> Path: 187 | try: 188 | subprocess.check_output( 189 | [ 190 | "pandoc", 191 | markdown_file.as_posix(), 192 | "--toc", 193 | "-o", 194 | epub_file.as_posix(), 195 | "--epub-cover-image=ebook/cover.png", 196 | ] 197 | ) 198 | except CalledProcessError as e: 199 | raise RuntimeError( 200 | f"failed to build {epub_file}: pandoc failed: {e.output.decode()}" 201 | ) 202 | 203 | return epub_file 204 | 205 | 206 | def main() -> None: 207 | """Build ebooks.""" 208 | with TemporaryDirectory() as raw_out_dir: 209 | out_dir = Path(raw_out_dir) 210 | 211 | logging.info("converting svg images to png...") 212 | converted_image_dir = convert_images( 213 | Path("images"), out_dir / "converted_images" 214 | ) 215 | 216 | languages = json.loads(Path("config.json").read_text())["languages"].keys() 217 | logging.info(f"building ebooks for languages {'/'.join(languages)}") 218 | 219 | for lang in languages: 220 | logging.info(f"{lang}: generating markdown...") 221 | markdown_file = compile_full_markdown( 222 | Path(lang), out_dir / f"{lang}.md", converted_image_dir 223 | ) 224 | 225 | logging.info(f"{lang}: building pdf...") 226 | pdf_file = build_pdf(markdown_file, out_dir / f"{lang}.pdf") 227 | 228 | logging.info(f"{lang}: building epub...") 229 | epub_file = build_epub(markdown_file, out_dir / f"{lang}.epub") 230 | 231 | shutil.copy(pdf_file, f"ebook/vulkan_tutorial_{lang}.pdf") 232 | shutil.copy(epub_file, f"ebook/vulkan_tutorial_{lang}.epub") 233 | 234 | logging.info("done") 235 | 236 | 237 | if __name__ == "__main__": 238 | try: 239 | main() 240 | except RuntimeError as e: 241 | logging.error(str(e)) 242 | -------------------------------------------------------------------------------- /code/.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | -------------------------------------------------------------------------------- /code/00_base_code.cpp: -------------------------------------------------------------------------------- 1 | #define GLFW_INCLUDE_VULKAN 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | const uint32_t WIDTH = 800; 9 | const uint32_t HEIGHT = 600; 10 | 11 | class HelloTriangleApplication { 12 | public: 13 | void run() { 14 | initWindow(); 15 | initVulkan(); 16 | mainLoop(); 17 | cleanup(); 18 | } 19 | 20 | private: 21 | GLFWwindow* window; 22 | 23 | void initWindow() { 24 | glfwInit(); 25 | 26 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 27 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 28 | 29 | window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); 30 | } 31 | 32 | void initVulkan() { 33 | 34 | } 35 | 36 | void mainLoop() { 37 | while (!glfwWindowShouldClose(window)) { 38 | glfwPollEvents(); 39 | } 40 | } 41 | 42 | void cleanup() { 43 | glfwDestroyWindow(window); 44 | 45 | glfwTerminate(); 46 | } 47 | }; 48 | 49 | int main() { 50 | HelloTriangleApplication app; 51 | 52 | try { 53 | app.run(); 54 | } catch (const std::exception& e) { 55 | std::cerr << e.what() << std::endl; 56 | return EXIT_FAILURE; 57 | } 58 | 59 | return EXIT_SUCCESS; 60 | } 61 | -------------------------------------------------------------------------------- /code/01_instance_creation.cpp: -------------------------------------------------------------------------------- 1 | #define GLFW_INCLUDE_VULKAN 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | const uint32_t WIDTH = 800; 9 | const uint32_t HEIGHT = 600; 10 | 11 | class HelloTriangleApplication { 12 | public: 13 | void run() { 14 | initWindow(); 15 | initVulkan(); 16 | mainLoop(); 17 | cleanup(); 18 | } 19 | 20 | private: 21 | GLFWwindow* window; 22 | 23 | VkInstance instance; 24 | 25 | void initWindow() { 26 | glfwInit(); 27 | 28 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 29 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 30 | 31 | window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); 32 | } 33 | 34 | void initVulkan() { 35 | createInstance(); 36 | } 37 | 38 | void mainLoop() { 39 | while (!glfwWindowShouldClose(window)) { 40 | glfwPollEvents(); 41 | } 42 | } 43 | 44 | void cleanup() { 45 | vkDestroyInstance(instance, nullptr); 46 | 47 | glfwDestroyWindow(window); 48 | 49 | glfwTerminate(); 50 | } 51 | 52 | void createInstance() { 53 | VkApplicationInfo appInfo{}; 54 | appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; 55 | appInfo.pApplicationName = "Hello Triangle"; 56 | appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); 57 | appInfo.pEngineName = "No Engine"; 58 | appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); 59 | appInfo.apiVersion = VK_API_VERSION_1_0; 60 | 61 | VkInstanceCreateInfo createInfo{}; 62 | createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; 63 | createInfo.pApplicationInfo = &appInfo; 64 | 65 | uint32_t glfwExtensionCount = 0; 66 | const char** glfwExtensions; 67 | glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); 68 | 69 | createInfo.enabledExtensionCount = glfwExtensionCount; 70 | createInfo.ppEnabledExtensionNames = glfwExtensions; 71 | 72 | createInfo.enabledLayerCount = 0; 73 | 74 | if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { 75 | throw std::runtime_error("failed to create instance!"); 76 | } 77 | } 78 | }; 79 | 80 | int main() { 81 | HelloTriangleApplication app; 82 | 83 | try { 84 | app.run(); 85 | } catch (const std::exception& e) { 86 | std::cerr << e.what() << std::endl; 87 | return EXIT_FAILURE; 88 | } 89 | 90 | return EXIT_SUCCESS; 91 | } 92 | -------------------------------------------------------------------------------- /code/02_validation_layers.cpp: -------------------------------------------------------------------------------- 1 | #define GLFW_INCLUDE_VULKAN 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | const uint32_t WIDTH = 800; 11 | const uint32_t HEIGHT = 600; 12 | 13 | const std::vector validationLayers = { 14 | "VK_LAYER_KHRONOS_validation" 15 | }; 16 | 17 | #ifdef NDEBUG 18 | const bool enableValidationLayers = false; 19 | #else 20 | const bool enableValidationLayers = true; 21 | #endif 22 | 23 | VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { 24 | auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); 25 | if (func != nullptr) { 26 | return func(instance, pCreateInfo, pAllocator, pDebugMessenger); 27 | } else { 28 | return VK_ERROR_EXTENSION_NOT_PRESENT; 29 | } 30 | } 31 | 32 | void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { 33 | auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); 34 | if (func != nullptr) { 35 | func(instance, debugMessenger, pAllocator); 36 | } 37 | } 38 | 39 | class HelloTriangleApplication { 40 | public: 41 | void run() { 42 | initWindow(); 43 | initVulkan(); 44 | mainLoop(); 45 | cleanup(); 46 | } 47 | 48 | private: 49 | GLFWwindow* window; 50 | 51 | VkInstance instance; 52 | VkDebugUtilsMessengerEXT debugMessenger; 53 | 54 | void initWindow() { 55 | glfwInit(); 56 | 57 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 58 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 59 | 60 | window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); 61 | } 62 | 63 | void initVulkan() { 64 | createInstance(); 65 | setupDebugMessenger(); 66 | } 67 | 68 | void mainLoop() { 69 | while (!glfwWindowShouldClose(window)) { 70 | glfwPollEvents(); 71 | } 72 | } 73 | 74 | void cleanup() { 75 | if (enableValidationLayers) { 76 | DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); 77 | } 78 | 79 | vkDestroyInstance(instance, nullptr); 80 | 81 | glfwDestroyWindow(window); 82 | 83 | glfwTerminate(); 84 | } 85 | 86 | void createInstance() { 87 | if (enableValidationLayers && !checkValidationLayerSupport()) { 88 | throw std::runtime_error("validation layers requested, but not available!"); 89 | } 90 | 91 | VkApplicationInfo appInfo{}; 92 | appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; 93 | appInfo.pApplicationName = "Hello Triangle"; 94 | appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); 95 | appInfo.pEngineName = "No Engine"; 96 | appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); 97 | appInfo.apiVersion = VK_API_VERSION_1_0; 98 | 99 | VkInstanceCreateInfo createInfo{}; 100 | createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; 101 | createInfo.pApplicationInfo = &appInfo; 102 | 103 | auto extensions = getRequiredExtensions(); 104 | createInfo.enabledExtensionCount = static_cast(extensions.size()); 105 | createInfo.ppEnabledExtensionNames = extensions.data(); 106 | 107 | VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; 108 | if (enableValidationLayers) { 109 | createInfo.enabledLayerCount = static_cast(validationLayers.size()); 110 | createInfo.ppEnabledLayerNames = validationLayers.data(); 111 | 112 | populateDebugMessengerCreateInfo(debugCreateInfo); 113 | createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; 114 | } else { 115 | createInfo.enabledLayerCount = 0; 116 | 117 | createInfo.pNext = nullptr; 118 | } 119 | 120 | if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { 121 | throw std::runtime_error("failed to create instance!"); 122 | } 123 | } 124 | 125 | void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { 126 | createInfo = {}; 127 | createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; 128 | 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; 129 | 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; 130 | createInfo.pfnUserCallback = debugCallback; 131 | } 132 | 133 | void setupDebugMessenger() { 134 | if (!enableValidationLayers) return; 135 | 136 | VkDebugUtilsMessengerCreateInfoEXT createInfo; 137 | populateDebugMessengerCreateInfo(createInfo); 138 | 139 | if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { 140 | throw std::runtime_error("failed to set up debug messenger!"); 141 | } 142 | } 143 | 144 | std::vector getRequiredExtensions() { 145 | uint32_t glfwExtensionCount = 0; 146 | const char** glfwExtensions; 147 | glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); 148 | 149 | std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); 150 | 151 | if (enableValidationLayers) { 152 | extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); 153 | } 154 | 155 | return extensions; 156 | } 157 | 158 | bool checkValidationLayerSupport() { 159 | uint32_t layerCount; 160 | vkEnumerateInstanceLayerProperties(&layerCount, nullptr); 161 | 162 | std::vector availableLayers(layerCount); 163 | vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); 164 | 165 | for (const char* layerName : validationLayers) { 166 | bool layerFound = false; 167 | 168 | for (const auto& layerProperties : availableLayers) { 169 | if (strcmp(layerName, layerProperties.layerName) == 0) { 170 | layerFound = true; 171 | break; 172 | } 173 | } 174 | 175 | if (!layerFound) { 176 | return false; 177 | } 178 | } 179 | 180 | return true; 181 | } 182 | 183 | static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { 184 | std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; 185 | 186 | return VK_FALSE; 187 | } 188 | }; 189 | 190 | int main() { 191 | HelloTriangleApplication app; 192 | 193 | try { 194 | app.run(); 195 | } catch (const std::exception& e) { 196 | std::cerr << e.what() << std::endl; 197 | return EXIT_FAILURE; 198 | } 199 | 200 | return EXIT_SUCCESS; 201 | } 202 | -------------------------------------------------------------------------------- /code/09_shader_base.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec3 fragColor; 4 | 5 | layout(location = 0) out vec4 outColor; 6 | 7 | void main() { 8 | outColor = vec4(fragColor, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /code/09_shader_base.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) out vec3 fragColor; 4 | 5 | vec2 positions[3] = vec2[]( 6 | vec2(0.0, -0.5), 7 | vec2(0.5, 0.5), 8 | vec2(-0.5, 0.5) 9 | ); 10 | 11 | vec3 colors[3] = vec3[]( 12 | vec3(1.0, 0.0, 0.0), 13 | vec3(0.0, 1.0, 0.0), 14 | vec3(0.0, 0.0, 1.0) 15 | ); 16 | 17 | void main() { 18 | gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 19 | fragColor = colors[gl_VertexIndex]; 20 | } 21 | -------------------------------------------------------------------------------- /code/18_shader_vertexbuffer.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec3 fragColor; 4 | 5 | layout(location = 0) out vec4 outColor; 6 | 7 | void main() { 8 | outColor = vec4(fragColor, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /code/18_shader_vertexbuffer.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec2 inPosition; 4 | layout(location = 1) in vec3 inColor; 5 | 6 | layout(location = 0) out vec3 fragColor; 7 | 8 | void main() { 9 | gl_Position = vec4(inPosition, 0.0, 1.0); 10 | fragColor = inColor; 11 | } 12 | -------------------------------------------------------------------------------- /code/22_shader_ubo.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec3 fragColor; 4 | 5 | layout(location = 0) out vec4 outColor; 6 | 7 | void main() { 8 | outColor = vec4(fragColor, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /code/22_shader_ubo.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 0) uniform UniformBufferObject { 4 | mat4 model; 5 | mat4 view; 6 | mat4 proj; 7 | } ubo; 8 | 9 | layout(location = 0) in vec2 inPosition; 10 | layout(location = 1) in vec3 inColor; 11 | 12 | layout(location = 0) out vec3 fragColor; 13 | 14 | void main() { 15 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); 16 | fragColor = inColor; 17 | } 18 | -------------------------------------------------------------------------------- /code/26_shader_textures.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 1) uniform sampler2D texSampler; 4 | 5 | layout(location = 0) in vec3 fragColor; 6 | layout(location = 1) in vec2 fragTexCoord; 7 | 8 | layout(location = 0) out vec4 outColor; 9 | 10 | void main() { 11 | outColor = texture(texSampler, fragTexCoord); 12 | } 13 | -------------------------------------------------------------------------------- /code/26_shader_textures.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 0) uniform UniformBufferObject { 4 | mat4 model; 5 | mat4 view; 6 | mat4 proj; 7 | } ubo; 8 | 9 | layout(location = 0) in vec2 inPosition; 10 | layout(location = 1) in vec3 inColor; 11 | layout(location = 2) in vec2 inTexCoord; 12 | 13 | layout(location = 0) out vec3 fragColor; 14 | layout(location = 1) out vec2 fragTexCoord; 15 | 16 | void main() { 17 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); 18 | fragColor = inColor; 19 | fragTexCoord = inTexCoord; 20 | } 21 | -------------------------------------------------------------------------------- /code/27_shader_depth.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 1) uniform sampler2D texSampler; 4 | 5 | layout(location = 0) in vec3 fragColor; 6 | layout(location = 1) in vec2 fragTexCoord; 7 | 8 | layout(location = 0) out vec4 outColor; 9 | 10 | void main() { 11 | outColor = texture(texSampler, fragTexCoord); 12 | } 13 | -------------------------------------------------------------------------------- /code/27_shader_depth.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 0) uniform UniformBufferObject { 4 | mat4 model; 5 | mat4 view; 6 | mat4 proj; 7 | } ubo; 8 | 9 | layout(location = 0) in vec3 inPosition; 10 | layout(location = 1) in vec3 inColor; 11 | layout(location = 2) in vec2 inTexCoord; 12 | 13 | layout(location = 0) out vec3 fragColor; 14 | layout(location = 1) out vec2 fragTexCoord; 15 | 16 | void main() { 17 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); 18 | fragColor = inColor; 19 | fragTexCoord = inTexCoord; 20 | } 21 | -------------------------------------------------------------------------------- /code/31_shader_compute.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | struct Particle { 4 | vec2 position; 5 | vec2 velocity; 6 | vec4 color; 7 | }; 8 | 9 | layout (binding = 0) uniform ParameterUBO { 10 | float deltaTime; 11 | } ubo; 12 | 13 | layout(std140, binding = 1) readonly buffer ParticleSSBOIn { 14 | Particle particlesIn[ ]; 15 | }; 16 | 17 | layout(std140, binding = 2) buffer ParticleSSBOOut { 18 | Particle particlesOut[ ]; 19 | }; 20 | 21 | layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in; 22 | 23 | void main() 24 | { 25 | uint index = gl_GlobalInvocationID.x; 26 | 27 | Particle particleIn = particlesIn[index]; 28 | 29 | particlesOut[index].position = particleIn.position + particleIn.velocity.xy * ubo.deltaTime; 30 | particlesOut[index].velocity = particleIn.velocity; 31 | 32 | // Flip movement at window border 33 | if ((particlesOut[index].position.x <= -1.0) || (particlesOut[index].position.x >= 1.0)) { 34 | particlesOut[index].velocity.x = -particlesOut[index].velocity.x; 35 | } 36 | if ((particlesOut[index].position.y <= -1.0) || (particlesOut[index].position.y >= 1.0)) { 37 | particlesOut[index].velocity.y = -particlesOut[index].velocity.y; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /code/31_shader_compute.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec3 fragColor; 4 | 5 | layout(location = 0) out vec4 outColor; 6 | 7 | void main() { 8 | 9 | vec2 coord = gl_PointCoord - vec2(0.5); 10 | outColor = vec4(fragColor, 0.5 - length(coord)); 11 | } 12 | -------------------------------------------------------------------------------- /code/31_shader_compute.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec2 inPosition; 4 | layout(location = 1) in vec4 inColor; 5 | 6 | layout(location = 0) out vec3 fragColor; 7 | 8 | void main() { 9 | 10 | gl_PointSize = 14.0; 11 | gl_Position = vec4(inPosition.xy, 1.0, 1.0); 12 | fragColor = inColor.rgb; 13 | } 14 | -------------------------------------------------------------------------------- /code/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | project (VulkanTutorial) 4 | 5 | find_package (glfw3 REQUIRED) 6 | find_package (glm REQUIRED) 7 | find_package (Vulkan REQUIRED) 8 | find_package (tinyobjloader REQUIRED) 9 | 10 | find_package (PkgConfig) 11 | pkg_get_variable (STB_INCLUDEDIR stb includedir) 12 | if (NOT STB_INCLUDEDIR) 13 | unset (STB_INCLUDEDIR) 14 | find_path (STB_INCLUDEDIR stb_image.h PATH_SUFFIXES stb) 15 | endif () 16 | if (NOT STB_INCLUDEDIR) 17 | message (FATAL_ERROR "stb_image.h not found") 18 | endif () 19 | 20 | add_executable (glslang::validator IMPORTED) 21 | find_program (GLSLANG_VALIDATOR "glslangValidator" HINTS $ENV{VULKAN_SDK}/bin REQUIRED) 22 | set_property (TARGET glslang::validator PROPERTY IMPORTED_LOCATION "${GLSLANG_VALIDATOR}") 23 | 24 | function (add_shaders_target TARGET) 25 | cmake_parse_arguments ("SHADER" "" "CHAPTER_NAME" "SOURCES" ${ARGN}) 26 | set (SHADERS_DIR ${SHADER_CHAPTER_NAME}/shaders) 27 | add_custom_command ( 28 | OUTPUT ${SHADERS_DIR} 29 | COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADERS_DIR} 30 | ) 31 | set (SHADERS ${SHADERS_DIR}/frag.spv ${SHADERS_DIR}/vert.spv) 32 | # Some chapters may have compute shaders in addition to vertex and fragment shaders, 33 | # so we conditionally check this and add them to the target 34 | string(FIND "${SHADER_SOURCES}" "${CHAPTER_SHADER}.comp" COMPUTE_SHADER_INDEX) 35 | if (${COMPUTE_SHADER_INDEX} GREATER -1) 36 | set (SHADERS ${SHADERS} ${SHADERS_DIR}/comp.spv) 37 | endif() 38 | add_custom_command ( 39 | OUTPUT ${SHADERS} 40 | COMMAND glslang::validator 41 | ARGS --target-env vulkan1.0 ${SHADER_SOURCES} --quiet 42 | WORKING_DIRECTORY ${SHADERS_DIR} 43 | DEPENDS ${SHADERS_DIR} ${SHADER_SOURCES} 44 | COMMENT "Compiling Shaders" 45 | VERBATIM 46 | ) 47 | add_custom_target (${TARGET} DEPENDS ${SHADERS}) 48 | endfunction () 49 | 50 | function (add_chapter CHAPTER_NAME) 51 | cmake_parse_arguments (CHAPTER "" "SHADER" "LIBS;TEXTURES;MODELS" ${ARGN}) 52 | 53 | add_executable (${CHAPTER_NAME} ${CHAPTER_NAME}.cpp) 54 | set_target_properties (${CHAPTER_NAME} PROPERTIES 55 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}) 56 | set_target_properties (${CHAPTER_NAME} PROPERTIES CXX_STANDARD 17) 57 | target_link_libraries (${CHAPTER_NAME} Vulkan::Vulkan glfw) 58 | target_include_directories (${CHAPTER_NAME} PRIVATE ${STB_INCLUDEDIR}) 59 | 60 | if (DEFINED CHAPTER_SHADER) 61 | set (CHAPTER_SHADER_TARGET ${CHAPTER_NAME}_shader) 62 | file (GLOB SHADER_SOURCES ${CHAPTER_SHADER}.frag ${CHAPTER_SHADER}.vert ${CHAPTER_SHADER}.comp) 63 | add_shaders_target (${CHAPTER_SHADER_TARGET} CHAPTER_NAME ${CHAPTER_NAME} SOURCES ${SHADER_SOURCES}) 64 | add_dependencies (${CHAPTER_NAME} ${CHAPTER_SHADER_TARGET}) 65 | endif () 66 | if (DEFINED CHAPTER_LIBS) 67 | target_link_libraries (${CHAPTER_NAME} ${CHAPTER_LIBS}) 68 | endif () 69 | if (DEFINED CHAPTER_MODELS) 70 | file (COPY ${CHAPTER_MODELS} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/models) 71 | endif () 72 | if (DEFINED CHAPTER_TEXTURES) 73 | file (COPY ${CHAPTER_TEXTURES} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/textures) 74 | endif () 75 | endfunction () 76 | 77 | add_chapter (00_base_code) 78 | 79 | add_chapter (01_instance_creation) 80 | 81 | add_chapter (02_validation_layers) 82 | 83 | add_chapter (03_physical_device_selection) 84 | 85 | add_chapter (04_logical_device) 86 | 87 | add_chapter (05_window_surface) 88 | 89 | add_chapter (06_swap_chain_creation) 90 | 91 | add_chapter (07_image_views) 92 | 93 | add_chapter (08_graphics_pipeline) 94 | 95 | add_chapter (09_shader_modules 96 | SHADER 09_shader_base) 97 | 98 | add_chapter (10_fixed_functions 99 | SHADER 09_shader_base) 100 | 101 | add_chapter (11_render_passes 102 | SHADER 09_shader_base) 103 | 104 | add_chapter (12_graphics_pipeline_complete 105 | SHADER 09_shader_base) 106 | 107 | add_chapter (13_framebuffers 108 | SHADER 09_shader_base) 109 | 110 | add_chapter (14_command_buffers 111 | SHADER 09_shader_base) 112 | 113 | add_chapter (15_hello_triangle 114 | SHADER 09_shader_base) 115 | 116 | add_chapter (16_frames_in_flight 117 | SHADER 09_shader_base) 118 | 119 | add_chapter (17_swap_chain_recreation 120 | SHADER 09_shader_base) 121 | 122 | add_chapter (18_vertex_input 123 | SHADER 18_shader_vertexbuffer 124 | LIBS glm::glm) 125 | 126 | add_chapter (19_vertex_buffer 127 | SHADER 18_shader_vertexbuffer 128 | LIBS glm::glm) 129 | 130 | add_chapter (20_staging_buffer 131 | SHADER 18_shader_vertexbuffer 132 | LIBS glm::glm) 133 | 134 | add_chapter (21_index_buffer 135 | SHADER 18_shader_vertexbuffer 136 | LIBS glm::glm) 137 | 138 | add_chapter (22_descriptor_set_layout 139 | SHADER 22_shader_ubo 140 | LIBS glm::glm) 141 | 142 | add_chapter (23_descriptor_sets 143 | SHADER 22_shader_ubo 144 | LIBS glm::glm) 145 | 146 | add_chapter (24_texture_image 147 | SHADER 22_shader_ubo 148 | TEXTURES ../images/texture.jpg 149 | LIBS glm::glm) 150 | 151 | add_chapter (25_sampler 152 | SHADER 22_shader_ubo 153 | TEXTURES ../images/texture.jpg 154 | LIBS glm::glm) 155 | 156 | add_chapter (26_texture_mapping 157 | SHADER 26_shader_textures 158 | TEXTURES ../images/texture.jpg 159 | LIBS glm::glm) 160 | 161 | add_chapter (27_depth_buffering 162 | SHADER 27_shader_depth 163 | TEXTURES ../images/texture.jpg 164 | LIBS glm::glm) 165 | 166 | add_chapter (28_model_loading 167 | SHADER 27_shader_depth 168 | MODELS ../resources/viking_room.obj 169 | TEXTURES ../resources/viking_room.png 170 | LIBS glm::glm tinyobjloader::tinyobjloader) 171 | 172 | add_chapter (29_mipmapping 173 | SHADER 27_shader_depth 174 | MODELS ../resources/viking_room.obj 175 | TEXTURES ../resources/viking_room.png 176 | LIBS glm::glm tinyobjloader::tinyobjloader) 177 | 178 | add_chapter (30_multisampling 179 | SHADER 27_shader_depth 180 | MODELS ../resources/viking_room.obj 181 | TEXTURES ../resources/viking_room.png 182 | LIBS glm::glm tinyobjloader::tinyobjloader) 183 | 184 | add_chapter (31_compute_shader 185 | SHADER 31_shader_compute 186 | LIBS glm::glm) 187 | -------------------------------------------------------------------------------- /code/incremental_patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Check if both a starting file and patch are provided 4 | if [ $# != 2 ]; then 5 | echo "usage: " 6 | echo "specified patch will be applied to first_file.cpp and every code file larger than it (from later chapters)" 7 | exit 1 8 | fi 9 | 10 | # Iterate over code files in order of increasing size 11 | # i.e. in order of chapters (every chapter adds code) 12 | apply_patch=false 13 | 14 | for f in `ls -Sr *.cpp` 15 | do 16 | # Apply patch on every code file including and after initial one 17 | if [ $f = $1 ] || [ $apply_patch = true ]; then 18 | apply_patch=true 19 | 20 | patch -f $f < $2 | grep -q "FAILED" > /dev/null 21 | if [ $? = 0 ]; then 22 | echo "failed to apply patch to $f" 23 | exit 1 24 | fi 25 | 26 | rm -f *.orig 27 | fi 28 | done 29 | 30 | echo "patch successfully applied to all files" 31 | exit 0 32 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Vulkan Tutorial", 3 | "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.", 4 | "author": "Alexander Overvoorde", 5 | "live": { 6 | "inherit_index": true, 7 | "clean_urls": true 8 | }, 9 | "html": { 10 | "theme": "vulkan-vulkan", 11 | "auto_landing": false, 12 | "breadcrumbs": true, 13 | "breadcrumb_separator": "Chevrons", 14 | "date_modified": false, 15 | "toggle_code": false, 16 | "float": false, 17 | "auto_toc": true, 18 | 19 | "links": { 20 | "GitHub Repository": "https://github.com/Overv/VulkanTutorial", 21 | "Support the website": "https://www.paypal.me/AOvervoorde", 22 | "Vulkan Specification": "https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/", 23 | "LunarG Vulkan SDK": "https://lunarg.com/vulkan-sdk/", 24 | "Vulkan Guide": "https://docs.vulkan.org/guide/latest/", 25 | "Vulkan Hardware Database": "https://vulkan.gpuinfo.org/", 26 | "Rust code": "https://github.com/bwasty/vulkan-tutorial-rs", 27 | "Java code": "https://github.com/Naitsirc98/Vulkan-Tutorial-Java", 28 | "Go code": "https://github.com/vkngwrapper/vulkan-tutorial", 29 | "Visual Studio 2019 samples": "https://github.com/jjYBdx4IL/VulkanTutorial-VisualStudioProjectFiles", 30 | "Chinese translation": "https://github.com/fangcun010/VulkanTutorialCN" 31 | } 32 | }, 33 | "ignore": { 34 | "files": ["README.md", "build_ebook.py","daux.patch",".gitignore"], 35 | "folders": ["ebook"] 36 | }, 37 | "languages": {"en": "English", "fr": "Français"}, 38 | "language": "en", 39 | "processor": "VulkanLinkProcessor" 40 | } 41 | -------------------------------------------------------------------------------- /ebook/cover.kra: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/ebook/cover.kra -------------------------------------------------------------------------------- /ebook/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/ebook/cover.png -------------------------------------------------------------------------------- /ebook/listings-setup.tex: -------------------------------------------------------------------------------- 1 | % Contents of listings-setup.tex 2 | \usepackage{xcolor} 3 | 4 | \lstset{ 5 | basicstyle=\ttfamily, 6 | numbers=left, 7 | keywordstyle=\color[rgb]{0.13,0.29,0.53}\bfseries, 8 | stringstyle=\color[rgb]{0.31,0.60,0.02}, 9 | commentstyle=\color[rgb]{0.56,0.35,0.01}\itshape, 10 | numberstyle=\footnotesize, 11 | stepnumber=1, 12 | numbersep=5pt, 13 | backgroundcolor=\color[RGB]{248,248,248}, 14 | showspaces=false, 15 | showstringspaces=false, 16 | showtabs=false, 17 | tabsize=2, 18 | captionpos=b, 19 | breaklines=true, 20 | breakatwhitespace=true, 21 | breakautoindent=true, 22 | escapeinside={\%*}{*)}, 23 | linewidth=\textwidth, 24 | basewidth=0.5em, 25 | } -------------------------------------------------------------------------------- /en/03_Drawing_a_triangle/00_Setup/00_Base_code.md: -------------------------------------------------------------------------------- 1 | ## General structure 2 | 3 | In the previous chapter you've created a Vulkan project with all of the proper 4 | configuration and tested it with the sample code. In this chapter we're starting 5 | from scratch with the following code: 6 | 7 | ```c++ 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | class HelloTriangleApplication { 15 | public: 16 | void run() { 17 | initVulkan(); 18 | mainLoop(); 19 | cleanup(); 20 | } 21 | 22 | private: 23 | void initVulkan() { 24 | 25 | } 26 | 27 | void mainLoop() { 28 | 29 | } 30 | 31 | void cleanup() { 32 | 33 | } 34 | }; 35 | 36 | int main() { 37 | HelloTriangleApplication app; 38 | 39 | try { 40 | app.run(); 41 | } catch (const std::exception& e) { 42 | std::cerr << e.what() << std::endl; 43 | return EXIT_FAILURE; 44 | } 45 | 46 | return EXIT_SUCCESS; 47 | } 48 | ``` 49 | 50 | We first include the Vulkan header from the LunarG SDK, which provides the 51 | functions, structures and enumerations. The `stdexcept` and `iostream` headers 52 | are included for reporting and propagating errors. The `cstdlib` 53 | header provides the `EXIT_SUCCESS` and `EXIT_FAILURE` macros. 54 | 55 | The program itself is wrapped into a class where we'll store the Vulkan objects 56 | as private class members and add functions to initiate each of them, which will 57 | be called from the `initVulkan` function. Once everything has been prepared, we 58 | enter the main loop to start rendering frames. We'll fill in the `mainLoop` 59 | function to include a loop that iterates until the window is closed in a moment. 60 | Once the window is closed and `mainLoop` returns, we'll make sure to deallocate 61 | the resources we've used in the `cleanup` function. 62 | 63 | If any kind of fatal error occurs during execution then we'll throw a 64 | `std::runtime_error` exception with a descriptive message, which will propagate 65 | back to the `main` function and be printed to the command prompt. To handle 66 | 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 67 | out that a certain required extension is not supported. 68 | 69 | Roughly every chapter that follows after this one will add one new function that 70 | will be called from `initVulkan` and one or more new Vulkan objects to the 71 | private class members that need to be freed at the end in `cleanup`. 72 | 73 | ## Resource management 74 | 75 | Just like each chunk of memory allocated with `malloc` requires a call to 76 | `free`, every Vulkan object that we create needs to be explicitly destroyed when 77 | we no longer need it. In C++ it is possible to perform automatic resource 78 | management using [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) 79 | or smart pointers provided in the `` header. However, I've chosen to be 80 | explicit about allocation and deallocation of Vulkan objects in this tutorial. 81 | After all, Vulkan's niche is to be explicit about every operation to avoid 82 | mistakes, so it's good to be explicit about the lifetime of objects to learn how 83 | the API works. 84 | 85 | After following this tutorial, you could implement automatic resource management 86 | by writing C++ classes that acquire Vulkan objects in their constructor and 87 | release them in their destructor, or by providing a custom deleter to either 88 | `std::unique_ptr` or `std::shared_ptr`, depending on your ownership requirements. 89 | RAII is the recommended model for larger Vulkan programs, but 90 | for learning purposes it's always good to know what's going on behind the 91 | scenes. 92 | 93 | Vulkan objects are either created directly with functions like `vkCreateXXX`, or 94 | allocated through another object with functions like `vkAllocateXXX`. After 95 | making sure that an object is no longer used anywhere, you need to destroy it 96 | with the counterparts `vkDestroyXXX` and `vkFreeXXX`. The parameters for these 97 | functions generally vary for different types of objects, but there is one 98 | parameter that they all share: `pAllocator`. This is an optional parameter that 99 | allows you to specify callbacks for a custom memory allocator. We will ignore 100 | this parameter in the tutorial and always pass `nullptr` as argument. 101 | 102 | ## Integrating GLFW 103 | 104 | Vulkan works perfectly fine without creating a window if you want to use it for 105 | off-screen rendering, but it's a lot more exciting to actually show something! 106 | First replace the `#include ` line with 107 | 108 | ```c++ 109 | #define GLFW_INCLUDE_VULKAN 110 | #include 111 | ``` 112 | 113 | That way GLFW will include its own definitions and automatically load the Vulkan 114 | header with it. Add a `initWindow` function and add a call to it from the `run` 115 | function before the other calls. We'll use that function to initialize GLFW and 116 | create a window. 117 | 118 | ```c++ 119 | void run() { 120 | initWindow(); 121 | initVulkan(); 122 | mainLoop(); 123 | cleanup(); 124 | } 125 | 126 | private: 127 | void initWindow() { 128 | 129 | } 130 | ``` 131 | 132 | The very first call in `initWindow` should be `glfwInit()`, which initializes 133 | the GLFW library. Because GLFW was originally designed to create an OpenGL 134 | context, we need to tell it to not create an OpenGL context with a subsequent 135 | call: 136 | 137 | ```c++ 138 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 139 | ``` 140 | 141 | Because handling resized windows takes special care that we'll look into later, 142 | disable it for now with another window hint call: 143 | 144 | ```c++ 145 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 146 | ``` 147 | 148 | All that's left now is creating the actual window. Add a `GLFWwindow* window;` 149 | private class member to store a reference to it and initialize the window with: 150 | 151 | ```c++ 152 | window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); 153 | ``` 154 | 155 | The first three parameters specify the width, height and title of the window. 156 | The fourth parameter allows you to optionally specify a monitor to open the 157 | window on and the last parameter is only relevant to OpenGL. 158 | 159 | It's a good idea to use constants instead of hardcoded width and height numbers 160 | because we'll be referring to these values a couple of times in the future. I've 161 | added the following lines above the `HelloTriangleApplication` class definition: 162 | 163 | ```c++ 164 | const uint32_t WIDTH = 800; 165 | const uint32_t HEIGHT = 600; 166 | ``` 167 | 168 | and replaced the window creation call with 169 | 170 | ```c++ 171 | window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); 172 | ``` 173 | 174 | You should now have a `initWindow` function that looks like this: 175 | 176 | ```c++ 177 | void initWindow() { 178 | glfwInit(); 179 | 180 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 181 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 182 | 183 | window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); 184 | } 185 | ``` 186 | 187 | To keep the application running until either an error occurs or the window is 188 | closed, we need to add an event loop to the `mainLoop` function as follows: 189 | 190 | ```c++ 191 | void mainLoop() { 192 | while (!glfwWindowShouldClose(window)) { 193 | glfwPollEvents(); 194 | } 195 | } 196 | ``` 197 | 198 | This code should be fairly self-explanatory. It loops and checks for events like 199 | pressing the X button until the window has been closed by the user. This is also 200 | the loop where we'll later call a function to render a single frame. 201 | 202 | Once the window is closed, we need to clean up resources by destroying it and 203 | terminating GLFW itself. This will be our first `cleanup` code: 204 | 205 | ```c++ 206 | void cleanup() { 207 | glfwDestroyWindow(window); 208 | 209 | glfwTerminate(); 210 | } 211 | ``` 212 | 213 | When you run the program now you should see a window titled `Vulkan` show up 214 | until the application is terminated by closing the window. Now that we have the 215 | skeleton for the Vulkan application, let's [create the first Vulkan object](!en/Drawing_a_triangle/Setup/Instance)! 216 | 217 | [C++ code](/code/00_base_code.cpp) 218 | -------------------------------------------------------------------------------- /en/03_Drawing_a_triangle/00_Setup/01_Instance.md: -------------------------------------------------------------------------------- 1 | ## Creating an instance 2 | 3 | The very first thing you need to do is initialize the Vulkan library by creating 4 | an *instance*. The instance is the connection between your application and the 5 | Vulkan library and creating it involves specifying some details about your 6 | application to the driver. 7 | 8 | Start by adding a `createInstance` function and invoking it in the 9 | `initVulkan` function. 10 | 11 | ```c++ 12 | void initVulkan() { 13 | createInstance(); 14 | } 15 | ``` 16 | 17 | Additionally add a data member to hold the handle to the instance: 18 | 19 | ```c++ 20 | private: 21 | VkInstance instance; 22 | ``` 23 | 24 | Now, to create an instance we'll first have to fill in a struct with some 25 | information about our application. This data is technically optional, but it may 26 | provide some useful information to the driver in order to optimize our specific 27 | application (e.g. because it uses a well-known graphics engine with 28 | certain special behavior). This struct is called `VkApplicationInfo`: 29 | 30 | ```c++ 31 | void createInstance() { 32 | VkApplicationInfo appInfo{}; 33 | appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; 34 | appInfo.pApplicationName = "Hello Triangle"; 35 | appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); 36 | appInfo.pEngineName = "No Engine"; 37 | appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); 38 | appInfo.apiVersion = VK_API_VERSION_1_0; 39 | } 40 | ``` 41 | 42 | As mentioned before, many structs in Vulkan require you to explicitly specify 43 | the type in the `sType` member. This is also one of the many structs with a 44 | `pNext` member that can point to extension information in the future. We're 45 | using value initialization here to leave it as `nullptr`. 46 | 47 | A lot of information in Vulkan is passed through structs instead of function 48 | parameters and we'll have to fill in one more struct to provide sufficient 49 | information for creating an instance. This next struct is not optional and tells 50 | the Vulkan driver which global extensions and validation layers we want to use. 51 | Global here means that they apply to the entire program and not a specific 52 | device, which will become clear in the next few chapters. 53 | 54 | ```c++ 55 | VkInstanceCreateInfo createInfo{}; 56 | createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; 57 | createInfo.pApplicationInfo = &appInfo; 58 | ``` 59 | 60 | The first two parameters are straightforward. The next two layers specify the 61 | desired global extensions. As mentioned in the overview chapter, Vulkan is a 62 | platform agnostic API, which means that you need an extension to interface with 63 | the window system. GLFW has a handy built-in function that returns the 64 | extension(s) it needs to do that which we can pass to the struct: 65 | 66 | ```c++ 67 | uint32_t glfwExtensionCount = 0; 68 | const char** glfwExtensions; 69 | 70 | glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); 71 | 72 | createInfo.enabledExtensionCount = glfwExtensionCount; 73 | createInfo.ppEnabledExtensionNames = glfwExtensions; 74 | ``` 75 | 76 | The last two members of the struct determine the global validation layers to 77 | enable. We'll talk about these more in-depth in the next chapter, so just leave 78 | these empty for now. 79 | 80 | ```c++ 81 | createInfo.enabledLayerCount = 0; 82 | ``` 83 | 84 | We've now specified everything Vulkan needs to create an instance and we can 85 | finally issue the `vkCreateInstance` call: 86 | 87 | ```c++ 88 | VkResult result = vkCreateInstance(&createInfo, nullptr, &instance); 89 | ``` 90 | 91 | As you'll see, the general pattern that object creation function parameters in 92 | Vulkan follow is: 93 | 94 | * Pointer to struct with creation info 95 | * Pointer to custom allocator callbacks, always `nullptr` in this tutorial 96 | * Pointer to the variable that stores the handle to the new object 97 | 98 | If everything went well then the handle to the instance was stored in the 99 | `VkInstance` class member. Nearly all Vulkan functions return a value of type 100 | `VkResult` that is either `VK_SUCCESS` or an error code. To check if the 101 | instance was created successfully, we don't need to store the result and can 102 | just use a check for the success value instead: 103 | 104 | ```c++ 105 | if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { 106 | throw std::runtime_error("failed to create instance!"); 107 | } 108 | ``` 109 | 110 | Now run the program to make sure that the instance is created successfully. 111 | 112 | ## Encountered VK_ERROR_INCOMPATIBLE_DRIVER: 113 | If using MacOS with the latest MoltenVK sdk, you may get `VK_ERROR_INCOMPATIBLE_DRIVER` 114 | 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` 115 | extension is mandatory. 116 | 117 | To get over this error, first add the `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` bit 118 | to `VkInstanceCreateInfo` struct's flags, then add `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME` 119 | to instance enabled extension list. 120 | 121 | Typically the code could be like this: 122 | ```c++ 123 | ... 124 | 125 | std::vector requiredExtensions; 126 | 127 | for(uint32_t i = 0; i < glfwExtensionCount; i++) { 128 | requiredExtensions.emplace_back(glfwExtensions[i]); 129 | } 130 | 131 | requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); 132 | 133 | createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; 134 | 135 | createInfo.enabledExtensionCount = (uint32_t) requiredExtensions.size(); 136 | createInfo.ppEnabledExtensionNames = requiredExtensions.data(); 137 | 138 | if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { 139 | throw std::runtime_error("failed to create instance!"); 140 | } 141 | ``` 142 | 143 | ## Checking for extension support 144 | 145 | If you look at the `vkCreateInstance` documentation then you'll see that one of 146 | the possible error codes is `VK_ERROR_EXTENSION_NOT_PRESENT`. We could simply 147 | specify the extensions we require and terminate if that error code comes back. 148 | That makes sense for essential extensions like the window system interface, but 149 | what if we want to check for optional functionality? 150 | 151 | To retrieve a list of supported extensions before creating an instance, there's 152 | the `vkEnumerateInstanceExtensionProperties` function. It takes a pointer to a 153 | variable that stores the number of extensions and an array of 154 | `VkExtensionProperties` to store details of the extensions. It also takes an 155 | optional first parameter that allows us to filter extensions by a specific 156 | validation layer, which we'll ignore for now. 157 | 158 | To allocate an array to hold the extension details we first need to know how 159 | many there are. You can request just the number of extensions by leaving the 160 | latter parameter empty: 161 | 162 | ```c++ 163 | uint32_t extensionCount = 0; 164 | vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); 165 | ``` 166 | 167 | Now allocate an array to hold the extension details (`include `): 168 | 169 | ```c++ 170 | std::vector extensions(extensionCount); 171 | ``` 172 | 173 | Finally we can query the extension details: 174 | 175 | ```c++ 176 | vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); 177 | ``` 178 | 179 | Each `VkExtensionProperties` struct contains the name and version of an 180 | extension. We can list them with a simple for loop (`\t` is a tab for 181 | indentation): 182 | 183 | ```c++ 184 | std::cout << "available extensions:\n"; 185 | 186 | for (const auto& extension : extensions) { 187 | std::cout << '\t' << extension.extensionName << '\n'; 188 | } 189 | ``` 190 | 191 | You can add this code to the `createInstance` function if you'd like to provide 192 | some details about the Vulkan support. As a challenge, try to create a function 193 | that checks if all of the extensions returned by 194 | `glfwGetRequiredInstanceExtensions` are included in the supported extensions 195 | list. 196 | 197 | ## Cleaning up 198 | 199 | The `VkInstance` should be only destroyed right before the program exits. It can 200 | be destroyed in `cleanup` with the `vkDestroyInstance` function: 201 | 202 | ```c++ 203 | void cleanup() { 204 | vkDestroyInstance(instance, nullptr); 205 | 206 | glfwDestroyWindow(window); 207 | 208 | glfwTerminate(); 209 | } 210 | ``` 211 | 212 | The parameters for the `vkDestroyInstance` function are straightforward. As 213 | mentioned in the previous chapter, the allocation and deallocation functions 214 | in Vulkan have an optional allocator callback that we'll ignore by passing 215 | `nullptr` to it. All of the other Vulkan resources that we'll create in the 216 | following chapters should be cleaned up before the instance is destroyed. 217 | 218 | Before continuing with the more complex steps after instance creation, it's time 219 | to evaluate our debugging options by checking out [validation layers](!en/Drawing_a_triangle/Setup/Validation_layers). 220 | 221 | [C++ code](/code/01_instance_creation.cpp) 222 | -------------------------------------------------------------------------------- /en/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | After selecting a physical device to use we need to set up a *logical device* to 4 | interface with it. The logical device creation process is similar to the 5 | instance creation process and describes the features we want to use. We also 6 | need to specify which queues to create now that we've queried which queue 7 | families are available. You can even create multiple logical devices from the 8 | same physical device if you have varying requirements. 9 | 10 | Start by adding a new class member to store the logical device handle in. 11 | 12 | ```c++ 13 | VkDevice device; 14 | ``` 15 | 16 | Next, add a `createLogicalDevice` function that is called from `initVulkan`. 17 | 18 | ```c++ 19 | void initVulkan() { 20 | createInstance(); 21 | setupDebugMessenger(); 22 | pickPhysicalDevice(); 23 | createLogicalDevice(); 24 | } 25 | 26 | void createLogicalDevice() { 27 | 28 | } 29 | ``` 30 | 31 | ## Specifying the queues to be created 32 | 33 | The creation of a logical device involves specifying a bunch of details in 34 | structs again, of which the first one will be `VkDeviceQueueCreateInfo`. This 35 | structure describes the number of queues we want for a single queue family. 36 | Right now we're only interested in a queue with graphics capabilities. 37 | 38 | ```c++ 39 | QueueFamilyIndices indices = findQueueFamilies(physicalDevice); 40 | 41 | VkDeviceQueueCreateInfo queueCreateInfo{}; 42 | queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; 43 | queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value(); 44 | queueCreateInfo.queueCount = 1; 45 | ``` 46 | 47 | The currently available drivers will only allow you to create a small number of 48 | queues for each queue family and you don't really need more than one. That's 49 | because you can create all of the command buffers on multiple threads and then 50 | submit them all at once on the main thread with a single low-overhead call. 51 | 52 | Vulkan lets you assign priorities to queues to influence the scheduling of 53 | command buffer execution using floating point numbers between `0.0` and `1.0`. 54 | This is required even if there is only a single queue: 55 | 56 | ```c++ 57 | float queuePriority = 1.0f; 58 | queueCreateInfo.pQueuePriorities = &queuePriority; 59 | ``` 60 | 61 | ## Specifying used device features 62 | 63 | The next information to specify is the set of device features that we'll be 64 | using. These are the features that we queried support for with 65 | `vkGetPhysicalDeviceFeatures` in the previous chapter, like geometry shaders. 66 | Right now we don't need anything special, so we can simply define it and leave 67 | everything to `VK_FALSE`. We'll come back to this structure once we're about to 68 | start doing more interesting things with Vulkan. 69 | 70 | ```c++ 71 | VkPhysicalDeviceFeatures deviceFeatures{}; 72 | ``` 73 | 74 | ## Creating the logical device 75 | 76 | With the previous two structures in place, we can start filling in the main 77 | `VkDeviceCreateInfo` structure. 78 | 79 | ```c++ 80 | VkDeviceCreateInfo createInfo{}; 81 | createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; 82 | ``` 83 | 84 | First add pointers to the queue creation info and device features structs: 85 | 86 | ```c++ 87 | createInfo.pQueueCreateInfos = &queueCreateInfo; 88 | createInfo.queueCreateInfoCount = 1; 89 | 90 | createInfo.pEnabledFeatures = &deviceFeatures; 91 | ``` 92 | 93 | The remainder of the information bears a resemblance to the 94 | `VkInstanceCreateInfo` struct and requires you to specify extensions and 95 | validation layers. The difference is that these are device specific this time. 96 | 97 | An example of a device specific extension is `VK_KHR_swapchain`, which allows 98 | you to present rendered images from that device to windows. It is possible that 99 | there are Vulkan devices in the system that lack this ability, for example 100 | because they only support compute operations. We will come back to this 101 | extension in the swap chain chapter. 102 | 103 | 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: 104 | 105 | ```c++ 106 | createInfo.enabledExtensionCount = 0; 107 | 108 | if (enableValidationLayers) { 109 | createInfo.enabledLayerCount = static_cast(validationLayers.size()); 110 | createInfo.ppEnabledLayerNames = validationLayers.data(); 111 | } else { 112 | createInfo.enabledLayerCount = 0; 113 | } 114 | ``` 115 | 116 | We won't need any device specific extensions for now. 117 | 118 | That's it, we're now ready to instantiate the logical device with a call to the 119 | appropriately named `vkCreateDevice` function. 120 | 121 | ```c++ 122 | if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { 123 | throw std::runtime_error("failed to create logical device!"); 124 | } 125 | ``` 126 | 127 | The parameters are the physical device to interface with, the queue and usage 128 | info we just specified, the optional allocation callbacks pointer and a pointer 129 | to a variable to store the logical device handle in. Similarly to the instance 130 | creation function, this call can return errors based on enabling non-existent 131 | extensions or specifying the desired usage of unsupported features. 132 | 133 | The device should be destroyed in `cleanup` with the `vkDestroyDevice` function: 134 | 135 | ```c++ 136 | void cleanup() { 137 | vkDestroyDevice(device, nullptr); 138 | ... 139 | } 140 | ``` 141 | 142 | Logical devices don't interact directly with instances, which is why it's not 143 | included as a parameter. 144 | 145 | ## Retrieving queue handles 146 | 147 | The queues are automatically created along with the logical device, but we don't 148 | have a handle to interface with them yet. First add a class member to store a 149 | handle to the graphics queue: 150 | 151 | ```c++ 152 | VkQueue graphicsQueue; 153 | ``` 154 | 155 | Device queues are implicitly cleaned up when the device is destroyed, so we 156 | don't need to do anything in `cleanup`. 157 | 158 | We can use the `vkGetDeviceQueue` function to retrieve queue handles for each 159 | queue family. The parameters are the logical device, queue family, queue index 160 | and a pointer to the variable to store the queue handle in. Because we're only 161 | creating a single queue from this family, we'll simply use index `0`. 162 | 163 | ```c++ 164 | vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); 165 | ``` 166 | 167 | With the logical device and queue handles we can now actually start using the 168 | graphics card to do things! In the next few chapters we'll set up the resources 169 | to present results to the window system. 170 | 171 | [C++ code](/code/04_logical_device.cpp) 172 | -------------------------------------------------------------------------------- /en/03_Drawing_a_triangle/01_Presentation/02_Image_views.md: -------------------------------------------------------------------------------- 1 | To use any `VkImage`, including those in the swap chain, in the render pipeline 2 | we have to create a `VkImageView` object. An image view is quite literally a 3 | view into an image. It describes how to access the image and which part of the 4 | image to access, for example if it should be treated as a 2D texture depth 5 | texture without any mipmapping levels. 6 | 7 | In this chapter we'll write a `createImageViews` function that creates a basic 8 | image view for every image in the swap chain so that we can use them as color 9 | targets later on. 10 | 11 | First add a class member to store the image views in: 12 | 13 | ```c++ 14 | std::vector swapChainImageViews; 15 | ``` 16 | 17 | Create the `createImageViews` function and call it right after swap chain 18 | creation. 19 | 20 | ```c++ 21 | void initVulkan() { 22 | createInstance(); 23 | setupDebugMessenger(); 24 | createSurface(); 25 | pickPhysicalDevice(); 26 | createLogicalDevice(); 27 | createSwapChain(); 28 | createImageViews(); 29 | } 30 | 31 | void createImageViews() { 32 | 33 | } 34 | ``` 35 | 36 | The first thing we need to do is resize the list to fit all of the image views 37 | we'll be creating: 38 | 39 | ```c++ 40 | void createImageViews() { 41 | swapChainImageViews.resize(swapChainImages.size()); 42 | 43 | } 44 | ``` 45 | 46 | Next, set up the loop that iterates over all of the swap chain images. 47 | 48 | ```c++ 49 | for (size_t i = 0; i < swapChainImages.size(); i++) { 50 | 51 | } 52 | ``` 53 | 54 | The parameters for image view creation are specified in a 55 | `VkImageViewCreateInfo` structure. The first few parameters are straightforward. 56 | 57 | ```c++ 58 | VkImageViewCreateInfo createInfo{}; 59 | createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 60 | createInfo.image = swapChainImages[i]; 61 | ``` 62 | 63 | The `viewType` and `format` fields specify how the image data should be 64 | interpreted. The `viewType` parameter allows you to treat images as 1D textures, 65 | 2D textures, 3D textures and cube maps. 66 | 67 | ```c++ 68 | createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; 69 | createInfo.format = swapChainImageFormat; 70 | ``` 71 | 72 | The `components` field allows you to swizzle the color channels around. For 73 | example, you can map all of the channels to the red channel for a monochrome 74 | texture. You can also map constant values of `0` and `1` to a channel. In our 75 | case we'll stick to the default mapping. 76 | 77 | ```c++ 78 | createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; 79 | createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; 80 | createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; 81 | createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; 82 | ``` 83 | 84 | The `subresourceRange` field describes what the image's purpose is and which 85 | part of the image should be accessed. Our images will be used as color targets 86 | without any mipmapping levels or multiple layers. 87 | 88 | ```c++ 89 | createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 90 | createInfo.subresourceRange.baseMipLevel = 0; 91 | createInfo.subresourceRange.levelCount = 1; 92 | createInfo.subresourceRange.baseArrayLayer = 0; 93 | createInfo.subresourceRange.layerCount = 1; 94 | ``` 95 | 96 | If you were working on a stereographic 3D application, then you would create a 97 | swap chain with multiple layers. You could then create multiple image views for 98 | each image representing the views for the left and right eyes by accessing 99 | different layers. 100 | 101 | Creating the image view is now a matter of calling `vkCreateImageView`: 102 | 103 | ```c++ 104 | if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { 105 | throw std::runtime_error("failed to create image views!"); 106 | } 107 | ``` 108 | 109 | Unlike images, the image views were explicitly created by us, so we need to add 110 | a similar loop to destroy them again at the end of the program: 111 | 112 | ```c++ 113 | void cleanup() { 114 | for (auto imageView : swapChainImageViews) { 115 | vkDestroyImageView(device, imageView, nullptr); 116 | } 117 | 118 | ... 119 | } 120 | ``` 121 | 122 | An image view is sufficient to start using an image as a texture, but it's not 123 | quite ready to be used as a render target just yet. That requires one more step 124 | of indirection, known as a framebuffer. But first we'll have to set up the 125 | graphics pipeline. 126 | 127 | [C++ code](/code/07_image_views.cpp) 128 | -------------------------------------------------------------------------------- /en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md: -------------------------------------------------------------------------------- 1 | Over the course of the next few chapters we'll be setting up a graphics pipeline 2 | that is configured to draw our first triangle. The graphics pipeline is the 3 | sequence of operations that take the vertices and textures of your meshes all 4 | the way to the pixels in the render targets. A simplified overview is displayed 5 | below: 6 | 7 | ![](/images/vulkan_simplified_pipeline.svg) 8 | 9 | The *input assembler* collects the raw vertex data from the buffers you specify 10 | and may also use an index buffer to repeat certain elements without having to 11 | duplicate the vertex data itself. 12 | 13 | The *vertex shader* is run for every vertex and generally applies 14 | transformations to turn vertex positions from model space to screen space. It 15 | also passes per-vertex data down the pipeline. 16 | 17 | The *tessellation shaders* allow you to subdivide geometry based on certain 18 | rules to increase the mesh quality. This is often used to make surfaces like 19 | brick walls and staircases look less flat when they are nearby. 20 | 21 | The *geometry shader* is run on every primitive (triangle, line, point) and can 22 | discard it or output more primitives than came in. This is similar to the 23 | tessellation shader, but much more flexible. However, it is not used much in 24 | today's applications because the performance is not that good on most graphics 25 | cards except for Intel's integrated GPUs. 26 | 27 | The *rasterization* stage discretizes the primitives into *fragments*. These are 28 | the pixel elements that they fill on the framebuffer. Any fragments that fall 29 | outside the screen are discarded and the attributes outputted by the vertex 30 | shader are interpolated across the fragments, as shown in the figure. Usually 31 | the fragments that are behind other primitive fragments are also discarded here 32 | because of depth testing. 33 | 34 | The *fragment shader* is invoked for every fragment that survives and determines 35 | which framebuffer(s) the fragments are written to and with which color and depth 36 | values. It can do this using the interpolated data from the vertex shader, which 37 | can include things like texture coordinates and normals for lighting. 38 | 39 | The *color blending* stage applies operations to mix different fragments that 40 | map to the same pixel in the framebuffer. Fragments can simply overwrite each 41 | other, add up or be mixed based upon transparency. 42 | 43 | Stages with a green color are known as *fixed-function* stages. These stages 44 | allow you to tweak their operations using parameters, but the way they work is 45 | predefined. 46 | 47 | Stages with an orange color on the other hand are `programmable`, which means 48 | that you can upload your own code to the graphics card to apply exactly the 49 | operations you want. This allows you to use fragment shaders, for example, to 50 | implement anything from texturing and lighting to ray tracers. These programs 51 | run on many GPU cores simultaneously to process many objects, like vertices and 52 | fragments in parallel. 53 | 54 | If you've used older APIs like OpenGL and Direct3D before, then you'll be used 55 | to being able to change any pipeline settings at will with calls like 56 | `glBlendFunc` and `OMSetBlendState`. The graphics pipeline in Vulkan is almost 57 | completely immutable, so you must recreate the pipeline from scratch if you want 58 | to change shaders, bind different framebuffers or change the blend function. The 59 | disadvantage is that you'll have to create a number of pipelines that represent 60 | all of the different combinations of states you want to use in your rendering 61 | operations. However, because all of the operations you'll be doing in the 62 | pipeline are known in advance, the driver can optimize for it much better. 63 | 64 | Some of the programmable stages are optional based on what you intend to do. For 65 | example, the tessellation and geometry stages can be disabled if you are just 66 | drawing simple geometry. If you are only interested in depth values then you can 67 | disable the fragment shader stage, which is useful for [shadow map](https://en.wikipedia.org/wiki/Shadow_mapping) 68 | generation. 69 | 70 | In the next chapter we'll first create the two programmable stages required to 71 | put a triangle onto the screen: the vertex shader and fragment shader. The 72 | fixed-function configuration like blending mode, viewport, rasterization will be 73 | set up in the chapter after that. The final part of setting up the graphics 74 | pipeline in Vulkan involves the specification of input and output framebuffers. 75 | 76 | Create a `createGraphicsPipeline` function that is called right after 77 | `createImageViews` in `initVulkan`. We'll work on this function throughout the 78 | following chapters. 79 | 80 | ```c++ 81 | void initVulkan() { 82 | createInstance(); 83 | setupDebugMessenger(); 84 | createSurface(); 85 | pickPhysicalDevice(); 86 | createLogicalDevice(); 87 | createSwapChain(); 88 | createImageViews(); 89 | createGraphicsPipeline(); 90 | } 91 | 92 | ... 93 | 94 | void createGraphicsPipeline() { 95 | 96 | } 97 | ``` 98 | 99 | [C++ code](/code/08_graphics_pipeline.cpp) 100 | -------------------------------------------------------------------------------- /en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md: -------------------------------------------------------------------------------- 1 | ## Setup 2 | 3 | Before we can finish creating the pipeline, we need to tell Vulkan about the 4 | framebuffer attachments that will be used while rendering. We need to specify 5 | how many color and depth buffers there will be, how many samples to use for each 6 | of them and how their contents should be handled throughout the rendering 7 | operations. All of this information is wrapped in a *render pass* object, for 8 | which we'll create a new `createRenderPass` function. Call this function from 9 | `initVulkan` before `createGraphicsPipeline`. 10 | 11 | ```c++ 12 | void initVulkan() { 13 | createInstance(); 14 | setupDebugMessenger(); 15 | createSurface(); 16 | pickPhysicalDevice(); 17 | createLogicalDevice(); 18 | createSwapChain(); 19 | createImageViews(); 20 | createRenderPass(); 21 | createGraphicsPipeline(); 22 | } 23 | 24 | ... 25 | 26 | void createRenderPass() { 27 | 28 | } 29 | ``` 30 | 31 | ## Attachment description 32 | 33 | In our case we'll have just a single color buffer attachment represented by one 34 | of the images from the swap chain. 35 | 36 | ```c++ 37 | void createRenderPass() { 38 | VkAttachmentDescription colorAttachment{}; 39 | colorAttachment.format = swapChainImageFormat; 40 | colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; 41 | } 42 | ``` 43 | 44 | The `format` of the color attachment should match the format of the swap chain 45 | images, and we're not doing anything with multisampling yet, so we'll stick to 1 46 | sample. 47 | 48 | ```c++ 49 | colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; 50 | colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; 51 | ``` 52 | 53 | The `loadOp` and `storeOp` determine what to do with the data in the attachment 54 | before rendering and after rendering. We have the following choices for 55 | `loadOp`: 56 | 57 | * `VK_ATTACHMENT_LOAD_OP_LOAD`: Preserve the existing contents of the attachment 58 | * `VK_ATTACHMENT_LOAD_OP_CLEAR`: Clear the values to a constant at the start 59 | * `VK_ATTACHMENT_LOAD_OP_DONT_CARE`: Existing contents are undefined; we don't 60 | care about them 61 | 62 | In our case we're going to use the clear operation to clear the framebuffer to 63 | black before drawing a new frame. There are only two possibilities for the 64 | `storeOp`: 65 | 66 | * `VK_ATTACHMENT_STORE_OP_STORE`: Rendered contents will be stored in memory and 67 | can be read later 68 | * `VK_ATTACHMENT_STORE_OP_DONT_CARE`: Contents of the framebuffer will be 69 | undefined after the rendering operation 70 | 71 | We're interested in seeing the rendered triangle on the screen, so we're going 72 | with the store operation here. 73 | 74 | ```c++ 75 | colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; 76 | colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; 77 | ``` 78 | 79 | The `loadOp` and `storeOp` apply to color and depth data, and `stencilLoadOp` / 80 | `stencilStoreOp` apply to stencil data. Our application won't do anything with 81 | the stencil buffer, so the results of loading and storing are irrelevant. 82 | 83 | ```c++ 84 | colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 85 | colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; 86 | ``` 87 | 88 | Textures and framebuffers in Vulkan are represented by `VkImage` objects with a 89 | certain pixel format, however the layout of the pixels in memory can change 90 | based on what you're trying to do with an image. 91 | 92 | Some of the most common layouts are: 93 | 94 | * `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`: Images used as color attachment 95 | * `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`: Images to be presented in the swap chain 96 | * `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`: Images to be used as destination for a 97 | memory copy operation 98 | 99 | We'll discuss this topic in more depth in the texturing chapter, but what's 100 | important to know right now is that images need to be transitioned to specific 101 | layouts that are suitable for the operation that they're going to be involved in 102 | next. 103 | 104 | The `initialLayout` specifies which layout the image will have before the render 105 | pass begins. The `finalLayout` specifies the layout to automatically transition 106 | to when the render pass finishes. Using `VK_IMAGE_LAYOUT_UNDEFINED` for 107 | `initialLayout` means that we don't care what previous layout the image was in. 108 | The caveat of this special value is that the contents of the image are not 109 | guaranteed to be preserved, but that doesn't matter since we're going to clear 110 | it anyway. We want the image to be ready for presentation using the swap chain 111 | after rendering, which is why we use `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` as 112 | `finalLayout`. 113 | 114 | ## Subpasses and attachment references 115 | 116 | A single render pass can consist of multiple subpasses. Subpasses are subsequent 117 | rendering operations that depend on the contents of framebuffers in previous 118 | passes, for example a sequence of post-processing effects that are applied one 119 | after another. If you group these rendering operations into one render pass, 120 | then Vulkan is able to reorder the operations and conserve memory bandwidth for 121 | possibly better performance. For our very first triangle, however, we'll stick 122 | to a single subpass. 123 | 124 | Every subpass references one or more of the attachments that we've described 125 | using the structure in the previous sections. These references are themselves 126 | `VkAttachmentReference` structs that look like this: 127 | 128 | ```c++ 129 | VkAttachmentReference colorAttachmentRef{}; 130 | colorAttachmentRef.attachment = 0; 131 | colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 132 | ``` 133 | 134 | The `attachment` parameter specifies which attachment to reference by its index 135 | in the attachment descriptions array. Our array consists of a single 136 | `VkAttachmentDescription`, so its index is `0`. The `layout` specifies which 137 | layout we would like the attachment to have during a subpass that uses this 138 | reference. Vulkan will automatically transition the attachment to this layout 139 | when the subpass is started. We intend to use the attachment to function as a 140 | color buffer and the `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` layout will give 141 | us the best performance, as its name implies. 142 | 143 | The subpass is described using a `VkSubpassDescription` structure: 144 | 145 | ```c++ 146 | VkSubpassDescription subpass{}; 147 | subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; 148 | ``` 149 | 150 | Vulkan may also support compute subpasses in the future, so we have to be 151 | explicit about this being a graphics subpass. Next, we specify the reference to 152 | the color attachment: 153 | 154 | ```c++ 155 | subpass.colorAttachmentCount = 1; 156 | subpass.pColorAttachments = &colorAttachmentRef; 157 | ``` 158 | 159 | The index of the attachment in this array is directly referenced from the 160 | fragment shader with the `layout(location = 0) out vec4 outColor` directive! 161 | 162 | The following other types of attachments can be referenced by a subpass: 163 | 164 | * `pInputAttachments`: Attachments that are read from a shader 165 | * `pResolveAttachments`: Attachments used for multisampling color attachments 166 | * `pDepthStencilAttachment`: Attachment for depth and stencil data 167 | * `pPreserveAttachments`: Attachments that are not used by this subpass, but for 168 | which the data must be preserved 169 | 170 | ## Render pass 171 | 172 | Now that the attachment and a basic subpass referencing it have been described, 173 | we can create the render pass itself. Create a new class member variable to hold 174 | the `VkRenderPass` object right above the `pipelineLayout` variable: 175 | 176 | ```c++ 177 | VkRenderPass renderPass; 178 | VkPipelineLayout pipelineLayout; 179 | ``` 180 | 181 | The render pass object can then be created by filling in the 182 | `VkRenderPassCreateInfo` structure with an array of attachments and subpasses. 183 | The `VkAttachmentReference` objects reference attachments using the indices of 184 | this array. 185 | 186 | ```c++ 187 | VkRenderPassCreateInfo renderPassInfo{}; 188 | renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; 189 | renderPassInfo.attachmentCount = 1; 190 | renderPassInfo.pAttachments = &colorAttachment; 191 | renderPassInfo.subpassCount = 1; 192 | renderPassInfo.pSubpasses = &subpass; 193 | 194 | if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { 195 | throw std::runtime_error("failed to create render pass!"); 196 | } 197 | ``` 198 | 199 | Just like the pipeline layout, the render pass will be referenced throughout the 200 | program, so it should only be cleaned up at the end: 201 | 202 | ```c++ 203 | void cleanup() { 204 | vkDestroyPipelineLayout(device, pipelineLayout, nullptr); 205 | vkDestroyRenderPass(device, renderPass, nullptr); 206 | ... 207 | } 208 | ``` 209 | 210 | That was a lot of work, but in the next chapter it all comes together to finally 211 | create the graphics pipeline object! 212 | 213 | [C++ code](/code/11_render_passes.cpp) / 214 | [Vertex shader](/code/09_shader_base.vert) / 215 | [Fragment shader](/code/09_shader_base.frag) 216 | -------------------------------------------------------------------------------- /en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md: -------------------------------------------------------------------------------- 1 | We can now combine all of the structures and objects from the previous chapters 2 | to create the graphics pipeline! Here's the types of objects we have now, as a 3 | quick recap: 4 | 5 | * Shader stages: the shader modules that define the functionality of the 6 | programmable stages of the graphics pipeline 7 | * Fixed-function state: all of the structures that define the fixed-function 8 | stages of the pipeline, like input assembly, rasterizer, viewport and color 9 | blending 10 | * Pipeline layout: the uniform and push values referenced by the shader that can 11 | be updated at draw time 12 | * Render pass: the attachments referenced by the pipeline stages and their usage 13 | 14 | All of these combined fully define the functionality of the graphics pipeline, 15 | so we can now begin filling in the `VkGraphicsPipelineCreateInfo` structure at 16 | the end of the `createGraphicsPipeline` function. But before the calls to 17 | `vkDestroyShaderModule` because these are still to be used during the creation. 18 | 19 | ```c++ 20 | VkGraphicsPipelineCreateInfo pipelineInfo{}; 21 | pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; 22 | pipelineInfo.stageCount = 2; 23 | pipelineInfo.pStages = shaderStages; 24 | ``` 25 | 26 | We start by referencing the array of `VkPipelineShaderStageCreateInfo` structs. 27 | 28 | ```c++ 29 | pipelineInfo.pVertexInputState = &vertexInputInfo; 30 | pipelineInfo.pInputAssemblyState = &inputAssembly; 31 | pipelineInfo.pViewportState = &viewportState; 32 | pipelineInfo.pRasterizationState = &rasterizer; 33 | pipelineInfo.pMultisampleState = &multisampling; 34 | pipelineInfo.pDepthStencilState = nullptr; // Optional 35 | pipelineInfo.pColorBlendState = &colorBlending; 36 | pipelineInfo.pDynamicState = &dynamicState; 37 | ``` 38 | 39 | Then we reference all of the structures describing the fixed-function stage. 40 | 41 | ```c++ 42 | pipelineInfo.layout = pipelineLayout; 43 | ``` 44 | 45 | After that comes the pipeline layout, which is a Vulkan handle rather than a 46 | struct pointer. 47 | 48 | ```c++ 49 | pipelineInfo.renderPass = renderPass; 50 | pipelineInfo.subpass = 0; 51 | ``` 52 | 53 | And finally we have the reference to the render pass and the index of the sub 54 | pass where this graphics pipeline will be used. It is also possible to use other 55 | render passes with this pipeline instead of this specific instance, but they 56 | have to be *compatible* with `renderPass`. The requirements for compatibility 57 | are described [here](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility), 58 | but we won't be using that feature in this tutorial. 59 | 60 | ```c++ 61 | pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional 62 | pipelineInfo.basePipelineIndex = -1; // Optional 63 | ``` 64 | 65 | There are actually two more parameters: `basePipelineHandle` and 66 | `basePipelineIndex`. Vulkan allows you to create a new graphics pipeline by 67 | deriving from an existing pipeline. The idea of pipeline derivatives is that it 68 | is less expensive to set up pipelines when they have much functionality in 69 | common with an existing pipeline and switching between pipelines from the same 70 | parent can also be done quicker. You can either specify the handle of an 71 | existing pipeline with `basePipelineHandle` or reference another pipeline that 72 | is about to be created by index with `basePipelineIndex`. Right now there is 73 | only a single pipeline, so we'll simply specify a null handle and an invalid 74 | index. These values are only used if the `VK_PIPELINE_CREATE_DERIVATIVE_BIT` 75 | flag is also specified in the `flags` field of `VkGraphicsPipelineCreateInfo`. 76 | 77 | Now prepare for the final step by creating a class member to hold the 78 | `VkPipeline` object: 79 | 80 | ```c++ 81 | VkPipeline graphicsPipeline; 82 | ``` 83 | 84 | And finally create the graphics pipeline: 85 | 86 | ```c++ 87 | if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { 88 | throw std::runtime_error("failed to create graphics pipeline!"); 89 | } 90 | ``` 91 | 92 | The `vkCreateGraphicsPipelines` function actually has more parameters than the 93 | usual object creation functions in Vulkan. It is designed to take multiple 94 | `VkGraphicsPipelineCreateInfo` objects and create multiple `VkPipeline` objects 95 | in a single call. 96 | 97 | The second parameter, for which we've passed the `VK_NULL_HANDLE` argument, 98 | references an optional `VkPipelineCache` object. A pipeline cache can be used to 99 | store and reuse data relevant to pipeline creation across multiple calls to 100 | `vkCreateGraphicsPipelines` and even across program executions if the cache is 101 | stored to a file. This makes it possible to significantly speed up pipeline 102 | creation at a later time. We'll get into this in the pipeline cache chapter. 103 | 104 | The graphics pipeline is required for all common drawing operations, so it 105 | should also only be destroyed at the end of the program: 106 | 107 | ```c++ 108 | void cleanup() { 109 | vkDestroyPipeline(device, graphicsPipeline, nullptr); 110 | vkDestroyPipelineLayout(device, pipelineLayout, nullptr); 111 | ... 112 | } 113 | ``` 114 | 115 | Now run your program to confirm that all this hard work has resulted in a 116 | successful pipeline creation! We are already getting quite close to seeing 117 | something pop up on the screen. In the next couple of chapters we'll set up the 118 | actual framebuffers from the swap chain images and prepare the drawing commands. 119 | 120 | [C++ code](/code/12_graphics_pipeline_complete.cpp) / 121 | [Vertex shader](/code/09_shader_base.vert) / 122 | [Fragment shader](/code/09_shader_base.frag) 123 | -------------------------------------------------------------------------------- /en/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md: -------------------------------------------------------------------------------- 1 | We've talked a lot about framebuffers in the past few chapters and we've set up 2 | the render pass to expect a single framebuffer with the same format as the swap 3 | chain images, but we haven't actually created any yet. 4 | 5 | The attachments specified during render pass creation are bound by wrapping them 6 | into a `VkFramebuffer` object. A framebuffer object references all of the 7 | `VkImageView` objects that represent the attachments. In our case that will be 8 | only a single one: the color attachment. However, the image that we have to use 9 | for the attachment depends on which image the swap chain returns when we retrieve one 10 | for presentation. That means that we have to create a framebuffer for all of the 11 | images in the swap chain and use the one that corresponds to the retrieved image 12 | at drawing time. 13 | 14 | To that end, create another `std::vector` class member to hold the framebuffers: 15 | 16 | ```c++ 17 | std::vector swapChainFramebuffers; 18 | ``` 19 | 20 | We'll create the objects for this array in a new function `createFramebuffers` 21 | that is called from `initVulkan` right after creating the graphics pipeline: 22 | 23 | ```c++ 24 | void initVulkan() { 25 | createInstance(); 26 | setupDebugMessenger(); 27 | createSurface(); 28 | pickPhysicalDevice(); 29 | createLogicalDevice(); 30 | createSwapChain(); 31 | createImageViews(); 32 | createRenderPass(); 33 | createGraphicsPipeline(); 34 | createFramebuffers(); 35 | } 36 | 37 | ... 38 | 39 | void createFramebuffers() { 40 | 41 | } 42 | ``` 43 | 44 | Start by resizing the container to hold all of the framebuffers: 45 | 46 | ```c++ 47 | void createFramebuffers() { 48 | swapChainFramebuffers.resize(swapChainImageViews.size()); 49 | } 50 | ``` 51 | 52 | We'll then iterate through the image views and create framebuffers from them: 53 | 54 | ```c++ 55 | for (size_t i = 0; i < swapChainImageViews.size(); i++) { 56 | VkImageView attachments[] = { 57 | swapChainImageViews[i] 58 | }; 59 | 60 | VkFramebufferCreateInfo framebufferInfo{}; 61 | framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; 62 | framebufferInfo.renderPass = renderPass; 63 | framebufferInfo.attachmentCount = 1; 64 | framebufferInfo.pAttachments = attachments; 65 | framebufferInfo.width = swapChainExtent.width; 66 | framebufferInfo.height = swapChainExtent.height; 67 | framebufferInfo.layers = 1; 68 | 69 | if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { 70 | throw std::runtime_error("failed to create framebuffer!"); 71 | } 72 | } 73 | ``` 74 | 75 | As you can see, creation of framebuffers is quite straightforward. We first need 76 | to specify with which `renderPass` the framebuffer needs to be compatible. You 77 | can only use a framebuffer with the render passes that it is compatible with, 78 | which roughly means that they use the same number and type of attachments. 79 | 80 | The `attachmentCount` and `pAttachments` parameters specify the `VkImageView` 81 | objects that should be bound to the respective attachment descriptions in 82 | the render pass `pAttachment` array. 83 | 84 | The `width` and `height` parameters are self-explanatory and `layers` refers to 85 | the number of layers in image arrays. Our swap chain images are single images, 86 | so the number of layers is `1`. 87 | 88 | We should delete the framebuffers before the image views and render pass that 89 | they are based on, but only after we've finished rendering: 90 | 91 | ```c++ 92 | void cleanup() { 93 | for (auto framebuffer : swapChainFramebuffers) { 94 | vkDestroyFramebuffer(device, framebuffer, nullptr); 95 | } 96 | 97 | ... 98 | } 99 | ``` 100 | 101 | We've now reached the milestone where we have all of the objects that are 102 | required for rendering. In the next chapter we're going to write the first 103 | actual drawing commands. 104 | 105 | [C++ code](/code/13_framebuffers.cpp) / 106 | [Vertex shader](/code/09_shader_base.vert) / 107 | [Fragment shader](/code/09_shader_base.frag) 108 | -------------------------------------------------------------------------------- /en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md: -------------------------------------------------------------------------------- 1 | ## Frames in flight 2 | 3 | Right now our render loop has one glaring flaw. We are required to wait on the 4 | previous frame to finish before we can start rendering the next which results 5 | in unnecessary idling of the host. 6 | 7 | 8 | 9 | The way to fix this is to allow multiple frames to be *in-flight* at once, that 10 | is to say, allow the rendering of one frame to not interfere with the recording 11 | of the next. How do we do this? Any resource that is accessed and modified 12 | during rendering must be duplicated. Thus, we need multiple command buffers, 13 | semaphores, and fences. In later chapters we will also add multiple instances 14 | of other resources, so we will see this concept reappear. 15 | 16 | Start by adding a constant at the top of the program that defines how many 17 | frames should be processed concurrently: 18 | 19 | ```c++ 20 | const int MAX_FRAMES_IN_FLIGHT = 2; 21 | ``` 22 | 23 | We choose the number 2 because we don't want the CPU to get *too* far ahead of 24 | the GPU. With 2 frames in flight, the CPU and the GPU can be working on their 25 | own tasks at the same time. If the CPU finishes early, it will wait till the 26 | GPU finishes rendering before submitting more work. With 3 or more frames in 27 | flight, the CPU could get ahead of the GPU, adding frames of latency. 28 | Generally, extra latency isn't desired. But giving the application control over 29 | the number of frames in flight is another example of Vulkan being explicit. 30 | 31 | Each frame should have its own command buffer, set of semaphores, and fence. 32 | Rename and then change them to be `std::vector`s of the objects: 33 | 34 | ```c++ 35 | std::vector commandBuffers; 36 | 37 | ... 38 | 39 | std::vector imageAvailableSemaphores; 40 | std::vector renderFinishedSemaphores; 41 | std::vector inFlightFences; 42 | ``` 43 | 44 | Then we need to create multiple command buffers. Rename `createCommandBuffer` 45 | to `createCommandBuffers`. Next we need to resize the command buffers vector 46 | to the size of `MAX_FRAMES_IN_FLIGHT`, alter the `VkCommandBufferAllocateInfo` 47 | to contain that many command buffers, and then change the destination to our 48 | vector of command buffers: 49 | 50 | ```c++ 51 | void createCommandBuffers() { 52 | commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); 53 | ... 54 | allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); 55 | 56 | if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { 57 | throw std::runtime_error("failed to allocate command buffers!"); 58 | } 59 | } 60 | ``` 61 | 62 | The `createSyncObjects` function should be changed to create all of the objects: 63 | 64 | ```c++ 65 | void createSyncObjects() { 66 | imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); 67 | renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); 68 | inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); 69 | 70 | VkSemaphoreCreateInfo semaphoreInfo{}; 71 | semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; 72 | 73 | VkFenceCreateInfo fenceInfo{}; 74 | fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; 75 | fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; 76 | 77 | for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { 78 | if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || 79 | vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || 80 | vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { 81 | 82 | throw std::runtime_error("failed to create synchronization objects for a frame!"); 83 | } 84 | } 85 | } 86 | ``` 87 | 88 | Similarly, they should also all be cleaned up: 89 | 90 | ```c++ 91 | void cleanup() { 92 | for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { 93 | vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); 94 | vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); 95 | vkDestroyFence(device, inFlightFences[i], nullptr); 96 | } 97 | 98 | ... 99 | } 100 | ``` 101 | 102 | Remember, because command buffers are freed for us when we free the command 103 | pool, there is nothing extra to do for command buffer cleanup. 104 | 105 | To use the right objects every frame, we need to keep track of the current 106 | frame. We will use a frame index for that purpose: 107 | 108 | ```c++ 109 | uint32_t currentFrame = 0; 110 | ``` 111 | 112 | The `drawFrame` function can now be modified to use the right objects: 113 | 114 | ```c++ 115 | void drawFrame() { 116 | vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); 117 | vkResetFences(device, 1, &inFlightFences[currentFrame]); 118 | 119 | vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); 120 | 121 | ... 122 | 123 | vkResetCommandBuffer(commandBuffers[currentFrame], 0); 124 | recordCommandBuffer(commandBuffers[currentFrame], imageIndex); 125 | 126 | ... 127 | 128 | submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; 129 | 130 | ... 131 | 132 | VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; 133 | 134 | ... 135 | 136 | VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; 137 | 138 | ... 139 | 140 | if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { 141 | } 142 | ``` 143 | 144 | Of course, we shouldn't forget to advance to the next frame every time: 145 | 146 | ```c++ 147 | void drawFrame() { 148 | ... 149 | 150 | currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; 151 | } 152 | ``` 153 | 154 | By using the modulo (%) operator, we ensure that the frame index loops around 155 | after every `MAX_FRAMES_IN_FLIGHT` enqueued frames. 156 | 157 | 159 | 160 | We've now implemented all the needed synchronization to ensure that there are 161 | no more than `MAX_FRAMES_IN_FLIGHT` frames of work enqueued and that these 162 | frames are not stepping over eachother. Note that it is fine for other parts of 163 | the code, like the final cleanup, to rely on more rough synchronization like 164 | `vkDeviceWaitIdle`. You should decide on which approach to use based on 165 | performance requirements. 166 | 167 | 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. 168 | 169 | 170 | In the next chapter we'll deal with one more small thing that is required for a 171 | well-behaved Vulkan program. 172 | 173 | 174 | [C++ code](/code/16_frames_in_flight.cpp) / 175 | [Vertex shader](/code/09_shader_base.vert) / 176 | [Fragment shader](/code/09_shader_base.frag) 177 | -------------------------------------------------------------------------------- /en/04_Vertex_buffers/00_Vertex_input_description.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | In the next few chapters, we're going to replace the hardcoded vertex data in 4 | the vertex shader with a vertex buffer in memory. We'll start with the easiest 5 | approach of creating a CPU visible buffer and using `memcpy` to copy the vertex 6 | data into it directly, and after that we'll see how to use a staging buffer to 7 | copy the vertex data to high performance memory. 8 | 9 | ## Vertex shader 10 | 11 | First change the vertex shader to no longer include the vertex data in the 12 | shader code itself. The vertex shader takes input from a vertex buffer using the 13 | `in` keyword. 14 | 15 | ```glsl 16 | #version 450 17 | 18 | layout(location = 0) in vec2 inPosition; 19 | layout(location = 1) in vec3 inColor; 20 | 21 | layout(location = 0) out vec3 fragColor; 22 | 23 | void main() { 24 | gl_Position = vec4(inPosition, 0.0, 1.0); 25 | fragColor = inColor; 26 | } 27 | ``` 28 | 29 | The `inPosition` and `inColor` variables are *vertex attributes*. They're 30 | properties that are specified per-vertex in the vertex buffer, just like we 31 | manually specified a position and color per vertex using the two arrays. Make 32 | sure to recompile the vertex shader! 33 | 34 | Just like `fragColor`, the `layout(location = x)` annotations assign indices to 35 | the inputs that we can later use to reference them. It is important to know that 36 | some types, like `dvec3` 64 bit vectors, use multiple *slots*. That means that 37 | the index after it must be at least 2 higher: 38 | 39 | ```glsl 40 | layout(location = 0) in dvec3 inPosition; 41 | layout(location = 2) in vec3 inColor; 42 | ``` 43 | 44 | You can find more info about the layout qualifier in the [OpenGL wiki](https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)). 45 | 46 | ## Vertex data 47 | 48 | We're moving the vertex data from the shader code to an array in the code of our 49 | program. Start by including the GLM library, which provides us with linear 50 | algebra related types like vectors and matrices. We're going to use these types 51 | to specify the position and color vectors. 52 | 53 | ```c++ 54 | #include 55 | ``` 56 | 57 | Create a new structure called `Vertex` with the two attributes that we're going 58 | to use in the vertex shader inside it: 59 | 60 | ```c++ 61 | struct Vertex { 62 | glm::vec2 pos; 63 | glm::vec3 color; 64 | }; 65 | ``` 66 | 67 | GLM conveniently provides us with C++ types that exactly match the vector types 68 | used in the shader language. 69 | 70 | ```c++ 71 | const std::vector vertices = { 72 | {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, 73 | {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, 74 | {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} 75 | }; 76 | ``` 77 | 78 | Now use the `Vertex` structure to specify an array of vertex data. We're using 79 | exactly the same position and color values as before, but now they're combined 80 | into one array of vertices. This is known as *interleaving* vertex attributes. 81 | 82 | ## Binding descriptions 83 | 84 | The next step is to tell Vulkan how to pass this data format to the vertex 85 | shader once it's been uploaded into GPU memory. There are two types of 86 | structures needed to convey this information. 87 | 88 | The first structure is `VkVertexInputBindingDescription` and we'll add a member 89 | function to the `Vertex` struct to populate it with the right data. 90 | 91 | ```c++ 92 | struct Vertex { 93 | glm::vec2 pos; 94 | glm::vec3 color; 95 | 96 | static VkVertexInputBindingDescription getBindingDescription() { 97 | VkVertexInputBindingDescription bindingDescription{}; 98 | 99 | return bindingDescription; 100 | } 101 | }; 102 | ``` 103 | 104 | A vertex binding describes at which rate to load data from memory throughout the 105 | vertices. It specifies the number of bytes between data entries and whether to 106 | move to the next data entry after each vertex or after each instance. 107 | 108 | ```c++ 109 | VkVertexInputBindingDescription bindingDescription{}; 110 | bindingDescription.binding = 0; 111 | bindingDescription.stride = sizeof(Vertex); 112 | bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; 113 | ``` 114 | 115 | All of our per-vertex data is packed together in one array, so we're only going 116 | to have one binding. The `binding` parameter specifies the index of the binding 117 | in the array of bindings. The `stride` parameter specifies the number of bytes 118 | from one entry to the next, and the `inputRate` parameter can have one of the 119 | following values: 120 | 121 | * `VK_VERTEX_INPUT_RATE_VERTEX`: Move to the next data entry after each vertex 122 | * `VK_VERTEX_INPUT_RATE_INSTANCE`: Move to the next data entry after each 123 | instance 124 | 125 | We're not going to use instanced rendering, so we'll stick to per-vertex data. 126 | 127 | ## Attribute descriptions 128 | 129 | The second structure that describes how to handle vertex input is 130 | `VkVertexInputAttributeDescription`. We're going to add another helper function 131 | to `Vertex` to fill in these structs. 132 | 133 | ```c++ 134 | #include 135 | 136 | ... 137 | 138 | static std::array getAttributeDescriptions() { 139 | std::array attributeDescriptions{}; 140 | 141 | return attributeDescriptions; 142 | } 143 | ``` 144 | 145 | As the function prototype indicates, there are going to be two of these 146 | structures. An attribute description struct describes how to extract a vertex 147 | attribute from a chunk of vertex data originating from a binding description. We 148 | have two attributes, position and color, so we need two attribute description 149 | structs. 150 | 151 | ```c++ 152 | attributeDescriptions[0].binding = 0; 153 | attributeDescriptions[0].location = 0; 154 | attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; 155 | attributeDescriptions[0].offset = offsetof(Vertex, pos); 156 | ``` 157 | 158 | The `binding` parameter tells Vulkan from which binding the per-vertex data 159 | comes. The `location` parameter references the `location` directive of the 160 | input in the vertex shader. The input in the vertex shader with location `0` is 161 | the position, which has two 32-bit float components. 162 | 163 | The `format` parameter describes the type of data for the attribute. A bit 164 | confusingly, the formats are specified using the same enumeration as color 165 | formats. The following shader types and formats are commonly used together: 166 | 167 | * `float`: `VK_FORMAT_R32_SFLOAT` 168 | * `vec2`: `VK_FORMAT_R32G32_SFLOAT` 169 | * `vec3`: `VK_FORMAT_R32G32B32_SFLOAT` 170 | * `vec4`: `VK_FORMAT_R32G32B32A32_SFLOAT` 171 | 172 | As you can see, you should use the format where the amount of color channels 173 | matches the number of components in the shader data type. It is allowed to use 174 | more channels than the number of components in the shader, but they will be 175 | silently discarded. If the number of channels is lower than the number of 176 | components, then the BGA components will use default values of `(0, 0, 1)`. The 177 | color type (`SFLOAT`, `UINT`, `SINT`) and bit width should also match the type 178 | of the shader input. See the following examples: 179 | 180 | * `ivec2`: `VK_FORMAT_R32G32_SINT`, a 2-component vector of 32-bit signed 181 | integers 182 | * `uvec4`: `VK_FORMAT_R32G32B32A32_UINT`, a 4-component vector of 32-bit 183 | unsigned integers 184 | * `double`: `VK_FORMAT_R64_SFLOAT`, a double-precision (64-bit) float 185 | 186 | The `format` parameter implicitly defines the byte size of attribute data and 187 | the `offset` parameter specifies the number of bytes since the start of the 188 | per-vertex data to read from. The binding is loading one `Vertex` at a time and 189 | the position attribute (`pos`) is at an offset of `0` bytes from the beginning 190 | of this struct. This is automatically calculated using the `offsetof` macro. 191 | 192 | ```c++ 193 | attributeDescriptions[1].binding = 0; 194 | attributeDescriptions[1].location = 1; 195 | attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; 196 | attributeDescriptions[1].offset = offsetof(Vertex, color); 197 | ``` 198 | 199 | The color attribute is described in much the same way. 200 | 201 | ## Pipeline vertex input 202 | 203 | We now need to set up the graphics pipeline to accept vertex data in this format 204 | by referencing the structures in `createGraphicsPipeline`. Find the 205 | `vertexInputInfo` struct and modify it to reference the two descriptions: 206 | 207 | ```c++ 208 | auto bindingDescription = Vertex::getBindingDescription(); 209 | auto attributeDescriptions = Vertex::getAttributeDescriptions(); 210 | 211 | vertexInputInfo.vertexBindingDescriptionCount = 1; 212 | vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); 213 | vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; 214 | vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); 215 | ``` 216 | 217 | The pipeline is now ready to accept vertex data in the format of the `vertices` 218 | container and pass it on to our vertex shader. If you run the program now with 219 | validation layers enabled, you'll see that it complains that there is no vertex 220 | buffer bound to the binding. The next step is to create a vertex buffer and move 221 | the vertex data to it so the GPU is able to access it. 222 | 223 | [C++ code](/code/18_vertex_input.cpp) / 224 | [Vertex shader](/code/18_shader_vertexbuffer.vert) / 225 | [Fragment shader](/code/18_shader_vertexbuffer.frag) 226 | -------------------------------------------------------------------------------- /en/04_Vertex_buffers/03_Index_buffer.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | The 3D meshes you'll be rendering in a real world application will often share 4 | vertices between multiple triangles. This already happens even with something 5 | simple like drawing a rectangle: 6 | 7 | ![](/images/vertex_vs_index.svg) 8 | 9 | Drawing a rectangle takes two triangles, which means that we need a vertex 10 | buffer with 6 vertices. The problem is that the data of two vertices needs to be 11 | duplicated resulting in 50% redundancy. It only gets worse with more complex 12 | meshes, where vertices are reused in an average number of 3 triangles. The 13 | solution to this problem is to use an *index buffer*. 14 | 15 | An index buffer is essentially an array of pointers into the vertex buffer. It 16 | allows you to reorder the vertex data, and reuse existing data for multiple 17 | vertices. The illustration above demonstrates what the index buffer would look 18 | like for the rectangle if we have a vertex buffer containing each of the four 19 | unique vertices. The first three indices define the upper-right triangle and the 20 | last three indices define the vertices for the bottom-left triangle. 21 | 22 | ## Index buffer creation 23 | 24 | In this chapter we're going to modify the vertex data and add index data to 25 | draw a rectangle like the one in the illustration. Modify the vertex data to 26 | represent the four corners: 27 | 28 | ```c++ 29 | const std::vector vertices = { 30 | {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, 31 | {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, 32 | {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, 33 | {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} 34 | }; 35 | ``` 36 | 37 | The top-left corner is red, top-right is green, bottom-right is blue and the 38 | bottom-left is white. We'll add a new array `indices` to represent the contents 39 | of the index buffer. It should match the indices in the illustration to draw the 40 | upper-right triangle and bottom-left triangle. 41 | 42 | ```c++ 43 | const std::vector indices = { 44 | 0, 1, 2, 2, 3, 0 45 | }; 46 | ``` 47 | 48 | It is possible to use either `uint16_t` or `uint32_t` for your index buffer 49 | depending on the number of entries in `vertices`. We can stick to `uint16_t` for 50 | now because we're using less than 65535 unique vertices. 51 | 52 | Just like the vertex data, the indices need to be uploaded into a `VkBuffer` for 53 | the GPU to be able to access them. Define two new class members to hold the 54 | resources for the index buffer: 55 | 56 | ```c++ 57 | VkBuffer vertexBuffer; 58 | VkDeviceMemory vertexBufferMemory; 59 | VkBuffer indexBuffer; 60 | VkDeviceMemory indexBufferMemory; 61 | ``` 62 | 63 | The `createIndexBuffer` function that we'll add now is almost identical to 64 | `createVertexBuffer`: 65 | 66 | ```c++ 67 | void initVulkan() { 68 | ... 69 | createVertexBuffer(); 70 | createIndexBuffer(); 71 | ... 72 | } 73 | 74 | void createIndexBuffer() { 75 | VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); 76 | 77 | VkBuffer stagingBuffer; 78 | VkDeviceMemory stagingBufferMemory; 79 | createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); 80 | 81 | void* data; 82 | vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); 83 | memcpy(data, indices.data(), (size_t) bufferSize); 84 | vkUnmapMemory(device, stagingBufferMemory); 85 | 86 | createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); 87 | 88 | copyBuffer(stagingBuffer, indexBuffer, bufferSize); 89 | 90 | vkDestroyBuffer(device, stagingBuffer, nullptr); 91 | vkFreeMemory(device, stagingBufferMemory, nullptr); 92 | } 93 | ``` 94 | 95 | There are only two notable differences. The `bufferSize` is now equal to the 96 | number of indices times the size of the index type, either `uint16_t` or 97 | `uint32_t`. The usage of the `indexBuffer` should be 98 | `VK_BUFFER_USAGE_INDEX_BUFFER_BIT` instead of 99 | `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`, which makes sense. Other than that, the 100 | process is exactly the same. We create a staging buffer to copy the contents of 101 | `indices` to and then copy it to the final device local index buffer. 102 | 103 | The index buffer should be cleaned up at the end of the program, just like the 104 | vertex buffer: 105 | 106 | ```c++ 107 | void cleanup() { 108 | cleanupSwapChain(); 109 | 110 | vkDestroyBuffer(device, indexBuffer, nullptr); 111 | vkFreeMemory(device, indexBufferMemory, nullptr); 112 | 113 | vkDestroyBuffer(device, vertexBuffer, nullptr); 114 | vkFreeMemory(device, vertexBufferMemory, nullptr); 115 | 116 | ... 117 | } 118 | ``` 119 | 120 | ## Using an index buffer 121 | 122 | Using an index buffer for drawing involves two changes to 123 | `recordCommandBuffer`. We first need to bind the index buffer, just like we did 124 | for the vertex buffer. The difference is that you can only have a single index 125 | buffer. It's unfortunately not possible to use different indices for each vertex 126 | attribute, so we do still have to completely duplicate vertex data even if just 127 | one attribute varies. 128 | 129 | ```c++ 130 | vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); 131 | 132 | vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); 133 | ``` 134 | 135 | An index buffer is bound with `vkCmdBindIndexBuffer` which has the index buffer, 136 | a byte offset into it, and the type of index data as parameters. As mentioned 137 | before, the possible types are `VK_INDEX_TYPE_UINT16` and 138 | `VK_INDEX_TYPE_UINT32`. 139 | 140 | Just binding an index buffer doesn't change anything yet, we also need to change 141 | the drawing command to tell Vulkan to use the index buffer. Remove the 142 | `vkCmdDraw` line and replace it with `vkCmdDrawIndexed`: 143 | 144 | ```c++ 145 | vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); 146 | ``` 147 | 148 | A call to this function is very similar to `vkCmdDraw`. The first two parameters 149 | specify the number of indices and the number of instances. We're not using 150 | instancing, so just specify `1` instance. The number of indices represents the 151 | number of vertices that will be passed to the vertex shader. The next parameter 152 | specifies an offset into the index buffer, using a value of `1` would cause the 153 | graphics card to start reading at the second index. The second to last parameter 154 | specifies an offset to add to the indices in the index buffer. The final 155 | parameter specifies an offset for instancing, which we're not using. 156 | 157 | Now run your program and you should see the following: 158 | 159 | ![](/images/indexed_rectangle.png) 160 | 161 | You now know how to save memory by reusing vertices with index buffers. This 162 | will become especially important in a future chapter where we're going to load 163 | complex 3D models. 164 | 165 | The previous chapter already mentioned that you should allocate multiple 166 | resources like buffers from a single memory allocation, but in fact you should 167 | go a step further. [Driver developers recommend](https://developer.nvidia.com/vulkan-memory-management) 168 | that you also store multiple buffers, like the vertex and index buffer, into a 169 | single `VkBuffer` and use offsets in commands like `vkCmdBindVertexBuffers`. The 170 | advantage is that your data is more cache friendly in that case, because it's 171 | closer together. It is even possible to reuse the same chunk of memory for 172 | multiple resources if they are not used during the same render operations, 173 | provided that their data is refreshed, of course. This is known as *aliasing* 174 | and some Vulkan functions have explicit flags to specify that you want to do 175 | this. 176 | 177 | [C++ code](/code/21_index_buffer.cpp) / 178 | [Vertex shader](/code/18_shader_vertexbuffer.vert) / 179 | [Fragment shader](/code/18_shader_vertexbuffer.frag) 180 | -------------------------------------------------------------------------------- /en/90_FAQ.md: -------------------------------------------------------------------------------- 1 | This page lists solutions to common problems that you may encounter while 2 | developing Vulkan applications. 3 | 4 | ## I get an access violation error in the core validation layer 5 | 6 | Make sure 7 | that MSI Afterburner / RivaTuner Statistics Server is not running, because it 8 | has some compatibility problems with Vulkan. 9 | 10 | ## I don't see any messages from the validation layers / Validation layers are not available 11 | 12 | First make sure that the validation layers get a chance to print errors by keeping the 13 | terminal open after your program exits. You can do this from Visual Studio by running 14 | your program with Ctrl-F5 instead of F5, and on Linux by executing your program from 15 | a terminal window. If there are still no messages and you are sure that validation 16 | layers are turned on, then you should ensure that your Vulkan SDK is correctly 17 | 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. 18 | 19 | ## vkCreateSwapchainKHR triggers an error in SteamOverlayVulkanLayer64.dll 20 | 21 | This appears to be a compatibility problem in the Steam client beta. There are a 22 | few possible workarounds: 23 | * Opt out of the Steam beta program. 24 | * Set the `DISABLE_VK_LAYER_VALVE_steam_overlay_1` environment variable to `1` 25 | * Delete the Steam overlay Vulkan layer entry in the registry under `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers` 26 | 27 | Example: 28 | 29 | ![](/images/steam_layers_env.png) 30 | 31 | ## vkCreateInstance fails with VK_ERROR_INCOMPATIBLE_DRIVER 32 | 33 | 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. 34 | 35 | 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. 36 | 37 | Code example: 38 | 39 | ```c++ 40 | ... 41 | 42 | std::vector requiredExtensions; 43 | 44 | for(uint32_t i = 0; i < glfwExtensionCount; i++) { 45 | requiredExtensions.emplace_back(glfwExtensions[i]); 46 | } 47 | 48 | requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); 49 | 50 | createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; 51 | 52 | createInfo.enabledExtensionCount = (uint32_t) requiredExtensions.size(); 53 | createInfo.ppEnabledExtensionNames = requiredExtensions.data(); 54 | 55 | if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { 56 | throw std::runtime_error("failed to create instance!"); 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /en/95_Privacy_policy.md: -------------------------------------------------------------------------------- 1 | ## General 2 | 3 | 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. 4 | 5 | ## Analytics 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | This data is not shared with third parties. 12 | 13 | ## Advertisement 14 | 15 | This website uses a third-party advertisement server that may use cookies to track activities on the website to measure engagement with advertisements. 16 | 17 | ## Comments 18 | 19 | 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. 20 | 21 | 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). -------------------------------------------------------------------------------- /fr/03_Dessiner_un_triangle/00_Mise_en_place/00_Code_de_base.md: -------------------------------------------------------------------------------- 1 | ## Structure générale 2 | 3 | Dans le chapitre précédent nous avons créé un projet Vulkan avec une configuration solide et nous l'avons testé. Nous 4 | recommençons ici à partir du code suivant : 5 | 6 | ```c++ 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class HelloTriangleApplication { 15 | public: 16 | void run() { 17 | initVulkan(); 18 | mainLoop(); 19 | cleanup(); 20 | } 21 | 22 | private: 23 | void initVulkan() { 24 | 25 | } 26 | 27 | void mainLoop() { 28 | 29 | } 30 | 31 | void cleanup() { 32 | 33 | } 34 | }; 35 | 36 | int main() { 37 | HelloTriangleApplication app; 38 | 39 | try { 40 | app.run(); 41 | } catch (const std::exception& e) { 42 | std::cerr << e.what() << std::endl;` 43 | return EXIT_FAILURE; 44 | } 45 | 46 | return EXIT_SUCCESS; 47 | } 48 | ``` 49 | 50 | Nous incluons d'abord le header Vulkan du SDK, qui fournit les fonctions, les structures et les énumérations. 51 | `` et `` nous permettront de reporter et de traiter les erreurs. Le header `` nous 52 | servira pour l'écriture d'une lambda dans la section sur la gestion des ressources. `` nous fournit les macros 53 | `EXIT_FAILURE` et `EXIT_SUCCESS` (optionnelles). 54 | 55 | Le programme est écrit à l'intérieur d'une classe, dans laquelle seront stockés les objets Vulkan. Nous avons également 56 | une fonction pour la création de chacun de ces objets. Une fois toute l'initialisation réalisée, nous entrons dans la 57 | boucle principale, qui attend que nous fermions la fenêtre pour quitter le programme, après avoir libéré grâce à la 58 | fonction cleanup toutes les ressources que nous avons allouées . 59 | 60 | Si nous rencontrons une quelconque erreur lors de l'exécution nous lèverons une `std::runtime_error` comportant un 61 | message descriptif, qui sera affiché sur le terminal depuis la fonction `main`. Afin de s'assurer que nous récupérons 62 | bien toutes les erreurs, nous utilisons `std::exception` dans le `catch`. Nous verrons bientôt que la requête de 63 | certaines extensions peut mener à lever des exceptions. 64 | 65 | À peu près tous les chapitres à partir de celui-ci introduiront une nouvelle fonction appelée dans `initVulkan` et un 66 | nouvel objet Vulkan qui sera justement créé par cette fonction. Il sera soit détruit dans `cleanup`, soit libéré 67 | automatiquement. 68 | 69 | ## Gestion des ressources 70 | 71 | De la même façon qu'une quelconque ressource explicitement allouée par `new` doit être explicitement libérée par `delete`, nous 72 | devrons explicitement détruire quasiment toutes les ressources Vulkan que nous allouerons. Il est possible d'exploiter 73 | des fonctionnalités du C++ pour s’acquitter automatiquement de cela. Ces possibilités sont localisées dans `` si 74 | vous désirez les utiliser. Cependant nous resterons explicites pour toutes les opérations dans ce tutoriel, car la 75 | puissance de Vulkan réside en particulier dans la clareté de l'expression de la volonté du programmeur. De plus, cela 76 | nous permettra de bien comprendre la durée de vie de chacun des objets. 77 | 78 | Après avoir suivi ce tutoriel vous pourrez parfaitement implémenter une gestion automatique des ressources en 79 | spécialisant `std::shared_ptr` par exemple. L'utilisation du [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) 80 | à votre avantage est toujours recommandé en C++ pour de gros programmes Vulkan, mais il est quand même bon de 81 | commencer par connaître les détails de l'implémentation. 82 | 83 | Les objets Vulkan peuvent être créés de deux manières. Soit ils sont directement créés avec une fonction du type 84 | `vkCreateXXX`, soit ils sont alloués à l'aide d'un autre objet avec une fonction `vkAllocateXXX`. Après vous 85 | être assuré qu'il n'est plus utilisé où que ce soit, il faut le détruire en utilisant les fonctions 86 | `vkDestroyXXX` ou `vkFreeXXX`, respectivement. Les paramètres de ces fonctions varient sauf pour l'un d'entre eux : 87 | `pAllocator`. Ce paramètre optionnel vous permet de spécifier un callback sur un allocateur de mémoire. Nous 88 | n'utiliserons jamais ce paramètre et indiquerons donc toujours `nullptr`. 89 | 90 | ## Intégrer GLFW 91 | 92 | Vulkan marche très bien sans fenêtre si vous voulez l'utiliser pour du rendu sans écran (offscreen rendering en 93 | Anglais), mais c'est tout de même plus intéressant d'afficher quelque chose! Remplacez d'abord la ligne 94 | `#include ` par : 95 | 96 | ```c++ 97 | #define GLFW_INCLUDE_VULKAN 98 | #include 99 | ``` 100 | 101 | GLFW va alors automatiquement inclure ses propres définitions des fonctions Vulkan et vous fournir le header Vulkan. 102 | Ajoutez une fonction `initWindow` et appelez-la depuis `run` avant les autres appels. Nous utiliserons cette fonction 103 | pour initialiser GLFW et créer une fenêtre. 104 | 105 | ```c++ 106 | void run() { 107 | initWindow(); 108 | initVulkan(); 109 | mainLoop(); 110 | cleanup(); 111 | } 112 | 113 | private: 114 | void initWindow() { 115 | 116 | } 117 | ``` 118 | 119 | Le premier appel dans `initWindow` doit être `glfwInit()`, ce qui initialise la librairie. Dans la mesure où GLFW a été 120 | créée pour fonctionner avec OpenGL, nous devons lui demander de ne pas créer de contexte OpenGL avec l'appel suivant : 121 | 122 | ```c++ 123 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 124 | ``` 125 | 126 | Dans la mesure où redimensionner une fenêtre n'est pas chose aisée avec Vulkan, nous verrons cela plus tard et 127 | l'interdisons pour l'instant. 128 | 129 | ```c++ 130 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 131 | ``` 132 | 133 | Il ne nous reste plus qu'à créer la fenêtre. Ajoutez un membre privé `GLFWWindow* m_window` pour en stocker une 134 | référence, et initialisez la ainsi : 135 | 136 | ```c++ 137 | window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); 138 | ``` 139 | 140 | Les trois premiers paramètres indiquent respectivement la largeur, la hauteur et le titre de la fenêtre. Le quatrième 141 | vous permet optionnellement de spécifier un moniteur sur lequel ouvrir la fenêtre, et le cinquième est spécifique à 142 | OpenGL. 143 | 144 | Nous devrions plutôt utiliser des constantes pour la hauteur et la largeur dans la mesure où nous aurons besoin de ces 145 | valeurs dans le futur. J'ai donc ajouté ceci au-dessus de la définition de la classe `HelloTriangleApplication` : 146 | 147 | ```c++ 148 | const uint32_t WIDTH = 800; 149 | const uint32_t HEIGHT = 600; 150 | ``` 151 | 152 | et remplacé la création de la fenêtre par : 153 | 154 | ```c++ 155 | window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); 156 | ``` 157 | 158 | Vous avez maintenant une fonction `initWindow` ressemblant à ceci : 159 | 160 | ```c++ 161 | void initWindow() { 162 | glfwInit(); 163 | 164 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 165 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 166 | 167 | window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); 168 | } 169 | ``` 170 | 171 | Pour s'assurer que l'application tourne jusqu'à ce qu'une erreur ou un clic sur la croix ne l'interrompe, nous 172 | devons écrire une petite boucle de gestion d'évènements : 173 | 174 | ```c++ 175 | void mainLoop() { 176 | while (!glfwWindowShouldClose(window)) { 177 | glfwPollEvents(); 178 | } 179 | } 180 | ``` 181 | 182 | Ce code est relativement simple. GLFW récupère tous les évènements disponibles, puis vérifie qu'aucun d'entre eux ne 183 | correspond à une demande de fermeture de fenêtre. Ce sera aussi ici que nous appellerons la fonction qui affichera un 184 | triangle. 185 | 186 | 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 187 | quitter GLFW. Voici notre première version de la fonction `cleanup` : 188 | 189 | ```c++ 190 | void cleanup() { 191 | glfwDestroyWindow(window); 192 | 193 | glfwTerminate(); 194 | } 195 | ``` 196 | 197 | Si vous lancez l'application, vous devriez voir une fenêtre appelée "Vulkan" qui se ferme en cliquant sur la croix. 198 | Maintenant que nous avons une base pour notre application Vulkan, [créons notre premier objet Vulkan!](!fr/Dessiner_un_triangle/Mise_en_place/Instance)! 199 | 200 | [Code C++](/code/00_base_code.cpp) 201 | -------------------------------------------------------------------------------- /fr/03_Dessiner_un_triangle/00_Mise_en_place/01_Instance.md: -------------------------------------------------------------------------------- 1 | ## Création d'une instance 2 | 3 | La première chose à faire avec Vulkan est son initialisation au travers d'une *instance*. Cette instance relie 4 | l'application à l'API. Pour la créer vous devrez donner quelques informations au driver. 5 | 6 | Créez une fonction `createInstance` et appelez-la depuis la fonction `initVulkan` : 7 | 8 | ```c++ 9 | void initVulkan() { 10 | createInstance(); 11 | } 12 | ``` 13 | 14 | Ajoutez ensuite un membre donnée représentant cette instance : 15 | 16 | ```c++ 17 | private: 18 | VkInstance instance; 19 | ``` 20 | 21 | Pour créer l'instance, nous allons d'abord remplir une première structure avec des informations sur notre application. 22 | Ces données sont optionnelles, mais elles peuvent fournir des informations utiles au driver pour optimiser ou 23 | diagnostiquer les erreurs lors de l'exécution, par exemple en reconnaissant le nom d'un moteur graphique. Cette structure 24 | s'appelle `VkApplicationInfo` : 25 | 26 | ```c++ 27 | void createInstance() { 28 | VkApplicationInfo appInfo{}; 29 | appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; 30 | appInfo.pApplicationName = "Hello Triangle"; 31 | appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); 32 | appInfo.pEngineName = "No Engine"; 33 | appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); 34 | appInfo.apiVersion = VK_API_VERSION_1_0; 35 | } 36 | ``` 37 | 38 | Comme mentionné précédemment, la plupart des structures Vulkan vous demandent d'expliciter leur propre type dans le 39 | membre `sType`. Cela permet d'indiquer la version exacte de la structure que nous voulons utiliser : il y aura dans 40 | le futur des extensions à celles-ci. Pour simplifier leur implémentation, les utiliser ne nécessitera que de changer 41 | le type `VK_STRUCTURE_TYPE_XXX` en `VK_STRUCTURE_TYPE_XXX_2` (ou plus de 2) et de fournir une structure complémentaire 42 | à l'aide du pointeur `pNext`. Nous n'utiliserons aucune extension, et donnerons donc toujours `nullptr` à `pNext`. 43 | 44 | Avec Vulkan, nous rencontrerons souvent (TRÈS souvent) des structures à remplir pour passer les informations à Vulkan. 45 | Nous allons maintenant remplir le reste de la structure permettant la création de l'instance. Celle-ci n'est pas 46 | optionnelle. Elle permet d'informer le driver des extensions et des validation layers que nous utiliserons, et ceci 47 | de manière globale. Globale siginifie ici que ces données ne serons pas spécifiques à un périphérique. Nous verrons 48 | la signification de cela dans les chapitres suivants. 49 | 50 | ```c++ 51 | VkInstanceCreateInfo createInfo{}; 52 | createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; 53 | createInfo.pApplicationInfo = &appInfo; 54 | ``` 55 | 56 | Les deux premiers paramètres sont simples. Les deux suivants spécifient les extensions dont nous aurons besoin. Comme 57 | nous l'avons vu dans l'introduction, Vulkan ne connaît pas la plateforme sur laquelle il travaille, et nous aurons donc 58 | besoin d'extensions pour utiliser des interfaces avec le gestionnaire de fenêtre. GLFW possède une fonction très 59 | pratique qui nous donne la liste des extensions dont nous aurons besoin pour afficher nos résultats. Remplissez donc la 60 | structure de ces données : 61 | 62 | ```c++ 63 | uint32_t glfwExtensionCount = 0; 64 | const char** glfwExtensions; 65 | 66 | glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); 67 | 68 | createInfo.enabledExtensionCount = glfwExtensionCount; 69 | createInfo.ppEnabledExtensionNames = glfwExtensions; 70 | ``` 71 | 72 | Les deux derniers membres de la structure indiquent les validations layers à activer. Nous verrons cela dans le prochain 73 | chapitre, laissez ces champs vides pour le moment : 74 | 75 | ```c++ 76 | createInfo.enabledLayerCount = 0; 77 | ``` 78 | 79 | Nous avons maintenant indiqué tout ce dont Vulkan a besoin pour créer notre première instance. Nous pouvons enfin 80 | appeler `vkCreateInstance` : 81 | 82 | ```c++ 83 | VkResult result = vkCreateInstance(&createInfo, nullptr, &instance); 84 | ``` 85 | 86 | Comme vous le reverrez, l'appel à une fonction pour la création d'un objet Vulkan a le prototype suivant : 87 | 88 | * Pointeur sur une structure contenant l'information pour la création 89 | * Pointeur sur une fonction d'allocation que nous laisserons toujours `nullptr` 90 | * Pointeur sur une variable stockant une référence au nouvel objet 91 | 92 | Si tout s'est bien passé, la référence à l'instance devrait être contenue dans le membre `VkInstance`. Quasiment toutes 93 | les fonctions Vulkan retournent une valeur de type VkResult, pouvant être soit `VK_SUCCESS` soit un code d'erreur. Afin 94 | de vérifier si la création de l'instance s'est bien déroulée nous pouvons placer l'appel dans un `if` : 95 | 96 | ```c++ 97 | if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { 98 | throw std::runtime_error("Echec de la création de l'instance!"); 99 | } 100 | ``` 101 | 102 | Lancez votre programme pour voir si l'instance s'est créée correctement. 103 | 104 | ## Vérification du support des extensions 105 | 106 | Si vous regardez la documentation pour `vkCreateInstance` vous pourrez voir que l'un des messages d'erreur possible est 107 | `VK_ERROR_EXTENSION_NOT_PRESENT`. Nous pourrions juste interrompre le programme et afficher une erreur si une extension 108 | manque. Ce serait logique pour des fonctionnalités cruciales comme l'affichage, mais pas dans le cas d'extensions 109 | optionnelles. 110 | 111 | La fonction `vkEnumerateInstanceExtensionProperties` permet de récupérer la totalité des extensions supportées par le 112 | système avant la création de l'instance. Elle demande un pointeur vers une variable stockant le nombre d'extensions 113 | supportées et un tableau où stocker des informations sur chacune des extensions. Elle possède également un paramètre 114 | optionnel permettant de filtrer les résultats pour une validation layer spécifique. Nous l'ignorerons pour le moment. 115 | 116 | Pour allouer un tableau contenant les détails des extensions nous devons déjà connaître le nombre de ces extensions. 117 | Vous pouvez ne demander que cette information en laissant le premier paramètre `nullptr` : 118 | 119 | ```c++ 120 | uint32_t extensionCount = 0; 121 | vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); 122 | ``` 123 | 124 | Nous utiliserons souvent cette méthode. Allouez maintenant un tableau pour stocker les détails des extensions (incluez 125 | ) : 126 | 127 | ```c++ 128 | std::vector extensions(extensionCount); 129 | ``` 130 | 131 | Nous pouvons désormais accéder aux détails des extensions : 132 | 133 | ```c++ 134 | vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); 135 | ``` 136 | 137 | Chacune des structure `VkExtensionProperties` contient le nom et la version maximale supportée de l'extension. Nous 138 | pouvons les afficher à l'aide d'une boucle `for` toute simple (`\t` représente une tabulation) : 139 | 140 | ```c++ 141 | std::cout << "Extensions disponibles :\n"; 142 | 143 | for (const auto& extension : extensions) { 144 | std::cout << '\t' << extension.extensionName << '\n'; 145 | } 146 | ``` 147 | 148 | Vous pouvez ajouter ce code dans la fonction `createInstance` si vous voulez indiquer des informations à propos du 149 | support Vulkan sur la machine. Petit challenge : programmez une fonction vérifiant si les extensions dont vous avez 150 | besoin (en particulier celles indiquées par GLFW) sont disponibles. 151 | 152 | ## Libération des ressources 153 | 154 | L'instance contenue dans `VkInstance` ne doit être détruite qu'à la fin du programme. Nous la détruirons dans la 155 | fonction `cleanup` grâce à la fonction `vkDestroyInstance` : 156 | 157 | ```c++ 158 | void cleanup() { 159 | vkDestroyInstance(instance, nullptr); 160 | 161 | glfwDestroyWindow(window); 162 | 163 | glfwTerminate(); 164 | } 165 | ``` 166 | 167 | Les paramètres de cette fonction sont évidents. Nous y retrouvons le paramètre pour un désallocateur que nous laissons 168 | `nullptr`. Toutes les ressources que nous allouerons à partir du prochain chapitre devront être libérées avant la 169 | libération de l'instance. 170 | 171 | Avant d'avancer dans les notions plus complexes, créons un moyen de déboger notre programme avec 172 | [les validations layers.](!fr/Dessiner_un_triangle/Mise_en_place/Validation_layers). 173 | 174 | [Code C++](/code/01_instance_creation.cpp) 175 | -------------------------------------------------------------------------------- /fr/03_Dessiner_un_triangle/00_Mise_en_place/04_Logical_device_et_queues.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | La sélection d'un physical device faite, nous devons générer un *logical device* pour servir d'interface. Le 4 | processus de sa création est similaire à celui de l'instance : nous devons décrire ce dont nous aurons besoin. Nous 5 | devons également spécifier les queues dont nous aurons besoin. Vous pouvez également créer plusieurs logical devices à 6 | partir d'un physical device si vous en avez besoin. 7 | 8 | Commencez par ajouter un nouveau membre donnée pour stocker la référence au logical device. 9 | 10 | ```c++ 11 | VkDevice device; 12 | ``` 13 | 14 | Ajoutez ensuite une fonction `createLogicalDevice` et appelez-la depuis `initVulkan`. 15 | 16 | ```c++ 17 | void initVulkan() { 18 | createInstance(); 19 | setupDebugMessenger(); 20 | pickPhysicalDevice(); 21 | createLogicalDevice(); 22 | } 23 | 24 | void createLogicalDevice() { 25 | 26 | } 27 | ``` 28 | 29 | ## Spécifier les queues à créer 30 | 31 | La création d'un logical device requiert encore que nous remplissions des informations dans des structures. La 32 | première de ces structures s'appelle `VkDeviceQueueCreateInfo`. Elle indique le nombre de queues que nous désirons pour 33 | chaque queue family. Pour le moment nous n'avons besoin que d'une queue originaire d'une unique queue family : la 34 | première avec un support pour les graphismes. 35 | 36 | ```c++ 37 | QueueFamilyIndices indices = findQueueFamilies(physicalDevice); 38 | 39 | VkDeviceQueueCreateInfo queueCreateInfo{}; 40 | queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; 41 | queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value(); 42 | queueCreateInfo.queueCount = 1; 43 | ``` 44 | 45 | Actuellement les drivers ne vous permettent que de créer un petit nombre de queues pour chacune des familles, et vous 46 | n'avez en effet pas besoin de plus. Vous pouvez très bien créer les commandes (command buffers) depuis plusieurs 47 | threads et les soumettre à la queue d'un coup sur le thread principal, et ce sans perte de performance. 48 | 49 | Vulkan permet d'assigner des niveaux de priorité aux queues à l'aide de floats compris entre `0.0` et `1.0`. Vous 50 | pouvez ainsi influencer l'exécution des command buffers. Il est nécessaire d'indiquer une priorité même lorsqu'une 51 | seule queue est présente : 52 | 53 | ```c++ 54 | float queuePriority = 1.0f; 55 | queueCreateInfo.pQueuePriorities = &queuePriority; 56 | ``` 57 | 58 | ## Spécifier les fonctionnalités utilisées 59 | 60 | Les prochaines informations à fournir sont les fonctionnalités du physical device que nous souhaitons utiliser. Ce 61 | sont celles dont nous avons vérifié la présence avec `vkGetPhysicalDeviceFeatures` dans le chapitre précédent. Nous 62 | n'avons besoin de rien de spécial pour l'instant, nous pouvons donc nous contenter de créer la structure et de tout 63 | laisser à `VK_FALSE`, valeur par défaut. Nous reviendrons sur cette structure quand nous ferons des choses plus 64 | intéressantes avec Vulkan. 65 | 66 | ```c++ 67 | VkPhysicalDeviceFeatures deviceFeatures{}; 68 | ``` 69 | 70 | ## Créer le logical device 71 | 72 | Avec ces deux structure prêtes, nous pouvons enfin remplir la structure principale appelée `VkDeviceCreateInfo`. 73 | 74 | ```c++ 75 | VkDeviceCreateInfo createInfo{}; 76 | createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; 77 | ``` 78 | 79 | Référencez d'abord les structures sur la création des queues et sur les fonctionnalités utilisées : 80 | 81 | ```c++ 82 | createInfo.pQueueCreateInfos = &queueCreateInfo; 83 | createInfo.queueCreateInfoCount = 1; 84 | 85 | createInfo.pEnabledFeatures = &deviceFeatures; 86 | ``` 87 | 88 | Le reste ressemble à la structure `VkInstanceCreateInfo`. Nous devons spécifier les extensions spécifiques de la 89 | carte graphique et les validation layers. 90 | 91 | Un exemple d'extension spécifique au GPU est `VK_KHR_swapchain`. Celle-ci vous permet de présenter à l'écran les images 92 | sur lesquels votre programme a effectué un rendu. Il est en effet possible que certains GPU ne possèdent pas cette 93 | capacité, par exemple parce qu'ils ne supportent que les compute shaders. Nous reviendrons sur cette extension 94 | dans le chapitre dédié à la swap chain. 95 | 96 | Comme dit dans le chapitre sur les validation layers, nous activerons les mêmes que celles que nous avons spécifiées 97 | lors de la création de l'instance. Nous n'avons pour l'instant besoin d'aucune validation layer en particulier. Notez 98 | que le standard ne fait plus la différence entre les extensions de l'instance et celles du device, au point que les 99 | paramètres `enabledLayerCount` et `ppEnabledLayerNames` seront probablement ignorés. Nous les remplissons quand même 100 | pour s'assurer de la bonne compatibilité avec les anciennes implémentations. 101 | 102 | ```c++ 103 | createInfo.enabledExtensionCount = 0; 104 | 105 | if (enableValidationLayers) { 106 | createInfo.enabledLayerCount = static_cast(validationLayers.size()); 107 | createInfo.ppEnabledLayerNames = validationLayers.data(); 108 | } else { 109 | createInfo.enabledLayerCount = 0; 110 | } 111 | ``` 112 | 113 | C'est bon, nous pouvons maintenant instancier le logical device en appelant la fonction `vkCreateDevice`. 114 | 115 | ```c++ 116 | if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { 117 | throw std::runtime_error("échec lors de la création d'un logical device!"); 118 | } 119 | ``` 120 | 121 | Les paramètres sont d'abord le physical device dont on souhaite extraire une interface, ensuite la structure contenant 122 | les informations, puis un pointeur optionnel pour l'allocation et enfin un pointeur sur la référence au logical 123 | 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. 124 | 125 | Le logical device doit être explicitement détruit dans la fonction `cleanup` avant le physical device : 126 | 127 | ```c++ 128 | void cleanup() { 129 | vkDestroyDevice(device, nullptr); 130 | ... 131 | } 132 | ``` 133 | 134 | Les logical devices n'interagissent pas directement avec l'instance mais seulement avec le physical device, c'est 135 | pourquoi il n'y a pas de paramètre pour l'instance. 136 | 137 | ## Récupérer des références aux queues 138 | 139 | Les queue families sont automatiquement crées avec le logical device. Cependant nous n'avons aucune interface avec 140 | elles. Ajoutez un membre donnée pour stocker une référence à la queue family supportant les graphismes : 141 | 142 | ```c++ 143 | VkQueue graphicsQueue; 144 | ``` 145 | 146 | Les queues sont implicitement détruites avec le logical device, nous n'avons donc pas à nous en charger dans `cleanup`. 147 | 148 | Nous pourrons ensuite récupérer des références à des queues avec la fonction `vkGetDeviceQueue`. Les paramètres en 149 | 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 150 | queue. Nous ne créons qu'une seule queue, nous écrirons donc `0` pour l'indice de la queue. 151 | 152 | ```c++ 153 | vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); 154 | ``` 155 | 156 | Avec le logical device et les queues nous allons maintenant pouvoir faire travailler la carte graphique! Dans le 157 | prochain chapitre nous mettrons en place les ressources nécessaires à la présentation des images à l'écran. 158 | 159 | [Code C++](/code/04_logical_device.cpp) 160 | -------------------------------------------------------------------------------- /fr/03_Dessiner_un_triangle/01_Présentation/02_Image_views.md: -------------------------------------------------------------------------------- 1 | Quelque soit la `VkImage` que nous voulons utiliser, dont celles de la swap chain, nous devons en créer une 2 | `VkImageView` pour la manipuler. Cette image view correspond assez litéralement à une vue dans l'image. Elle décrit 3 | 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 4 | texture 2D pour la profondeur sans aucun niveau de mipmapping. 5 | 6 | Dans ce chapitre nous écrirons une fonction `createImageViews` pour créer une image view basique pour chacune des 7 | images dans la swap chain, pour que nous puissions les utiliser comme cibles de couleur. 8 | 9 | Ajoutez d'abord un membre donnée pour y stocker une image view : 10 | 11 | ```c++ 12 | std::vector swapChainImageViews; 13 | ``` 14 | 15 | Créez la fonction `createImageViews` et appelez-la juste après la création de la swap chain. 16 | 17 | ```c++ 18 | void initVulkan() { 19 | createInstance(); 20 | setupDebugMessenger(); 21 | createSurface(); 22 | pickPhysicalDevice(); 23 | createLogicalDevice(); 24 | createSwapChain(); 25 | createImageViews(); 26 | } 27 | 28 | void createImageViews() { 29 | 30 | } 31 | ``` 32 | 33 | Nous devons d'abord redimensionner la liste pour pouvoir y mettre toutes les image views que nous créerons : 34 | 35 | ```c++ 36 | void createImageViews() { 37 | swapChainImageViews.resize(swapChainImages.size()); 38 | 39 | } 40 | ``` 41 | 42 | Créez ensuite la boucle qui parcourra toutes les images de la swap chain. 43 | 44 | ```c++ 45 | for (size_t i = 0; i < swapChainImages.size(); i++) { 46 | 47 | } 48 | ``` 49 | 50 | Les paramètres pour la création d'image views se spécifient dans la structure `VkImageViewCreateInfo`. Les deux 51 | premiers paramètres sont assez simples : 52 | 53 | ```c++ 54 | VkImageViewCreateInfo createInfo{}; 55 | createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 56 | createInfo.image = swapChainImages[i]; 57 | ``` 58 | 59 | Les champs `viewType` et `format` indiquent la manière dont les images doivent être interprétées. Le paramètre 60 | `viewType` permet de traiter les images comme des textures 1D, 2D, 3D ou cube map. 61 | 62 | ```c++ 63 | createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; 64 | createInfo.format = swapChainImageFormat; 65 | ``` 66 | 67 | Le champ `components` vous permet d'altérer les canaux de couleur. Par exemple, vous pouvez envoyer tous les 68 | canaux au canal rouge pour obtenir une texture monochrome. Vous pouvez aussi donner les valeurs constantes `0` ou `1` 69 | à un canal. Dans notre cas nous garderons les paramètres par défaut. 70 | 71 | ```c++ 72 | createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; 73 | createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; 74 | createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; 75 | createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; 76 | ``` 77 | 78 | Le champ `subresourceRange` décrit l'utilisation de l'image et indique quelles parties de l'image devraient être 79 | accédées. Notre image sera utilisée comme cible de couleur et n'aura ni mipmapping ni plusieurs couches. 80 | 81 | ```c++ 82 | createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 83 | createInfo.subresourceRange.baseMipLevel = 0; 84 | createInfo.subresourceRange.levelCount = 1; 85 | createInfo.subresourceRange.baseArrayLayer = 0; 86 | createInfo.subresourceRange.layerCount = 1; 87 | ``` 88 | 89 | Si vous travailliez sur une application 3D stéréoscopique, vous devrez alors créer une swap chain avec plusieurs 90 | couches. Vous pourriez alors créer plusieurs image views pour chaque image. Elles représenteront ce qui sera affiché 91 | pour l'œil gauche et pour l'œil droit. 92 | 93 | Créer l'image view ne se résume plus qu'à appeler `vkCreateImageView` : 94 | 95 | ```c++ 96 | if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { 97 | throw std::runtime_error("échec de la création d'une image view!"); 98 | } 99 | ``` 100 | 101 | À la différence des images, nous avons créé les image views explicitement et devons donc les détruire de la même 102 | manière, ce que nous faisons à l'aide d'une boucle : 103 | 104 | ```c++ 105 | void cleanup() { 106 | for (auto imageView : swapChainImageViews) { 107 | vkDestroyImageView(device, imageView, nullptr); 108 | } 109 | 110 | ... 111 | } 112 | ``` 113 | 114 | Une image view est suffisante pour commencer à utiliser une image comme une texture, mais pas pour que l'image soit 115 | utilisée comme cible d'affichage. Pour cela nous avons encore une étape, appelée framebuffer. Mais nous devons 116 | d'abord mettre en place le pipeline graphique. 117 | 118 | [Code C++](/code/07_image_views.cpp) 119 | -------------------------------------------------------------------------------- /fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/00_Introduction.md: -------------------------------------------------------------------------------- 1 | Dans les chapitres qui viennent nous allons configurer une pipeline graphique pour qu'elle affiche notre premier 2 | triangle. La pipeline graphique est l'ensemble des opérations qui prennent les vertices et les textures de vos 3 | éléments et les utilisent pour en faire des pixels sur les cibles d'affichage. Un résumé simplifié ressemble à ceci : 4 | 5 | ![](/images/vulkan_simplified_pipeline.svg) 6 | 7 | L'_input assembler_ collecte les données des sommets à partir des buffers que vous avez mis en place, et peut aussi 8 | utiliser un _index buffer_ pour répéter certains éléments sans avoir à stocker deux fois les mêmes données dans un 9 | buffer. 10 | 11 | Le _vertex shader_ est exécuté pour chaque sommet et leur applique en général des transformations pour que leurs 12 | coordonnées passent de l'espace du modèle (model space) à l'espace de l'écran (screen space). Il fournit ensuite des 13 | données à la suite de la pipeline. 14 | 15 | Les _tesselation shaders_ permettent de subdiviser la géométrie selon des règles paramétrables afin d'améliorer la 16 | qualité du rendu. Ce procédé est notamment utilisé pour que des surface comme les murs de briques ou les escaliers 17 | aient l'air moins plats lorsque l'on s'en approche. 18 | 19 | Le _geometry shader_ est invoqué pour chaque primitive (triangle, ligne, points...) et peut les détruire ou en créer 20 | de nouvelles, du même type ou non. Ce travail est similaire au tesselation shader tout en étant beaucoup plus 21 | flexible. Il n'est cependant pas beaucoup utilisé à cause de performances assez moyennes sur les cartes graphiques 22 | (avec comme exception les GPUs intégrés d'Intel). 23 | 24 | La _rasterization_ transforme les primitives en _fragments_. Ce sont les pixels auxquels les primitives correspondent 25 | sur le framebuffer. Tout fragment en dehors de l'écran est abandonné. Les attributs sortant du vertex shader 26 | sont interpolés lorsqu'ils sont donnés aux étapes suivantes. Les fragments cachés par d'autres fragments sont aussi 27 | quasiment toujours éliminés grâce au test de profondeur (depth testing). 28 | 29 | Le _fragment shader_ est invoqué pour chaque fragment restant et détermine à quel(s) framebuffer(s) le fragment 30 | est envoyé, et quelles données y sont inscrites. Il réalise ce travail à l'aide des données interpolées émises par le 31 | vertex shader, ce qui inclut souvent des coordonnées de texture et des normales pour réaliser des calculs d'éclairage. 32 | 33 | Le _color blending_ applique des opérations pour mixer différents fragments correspondant à un même pixel sur le 34 | framebuffer. Les fragments peuvent remplacer les valeurs des autres, s'additionner ou se mélanger selon les 35 | paramètres de transparence (ou plus correctement de translucidité, en anglais translucency). 36 | 37 | Les étapes écrites en vert sur le diagramme s'appellent _fixed-function stages_ (étapes à fonction fixée). Il est 38 | possible de modifier des paramètres influençant les calculs, mais pas de modifier les calculs eux-mêmes. 39 | 40 | Les étapes colorées en orange sont programmables, ce qui signifie que vous pouvez charger votre propre code dans la 41 | carte graphique pour y appliquer exactement ce que vous voulez. Cela vous permet par exemple d'utiliser les fragment 42 | shaders pour implémenter n'importe quoi, de l'utilisation de textures et d'éclairage jusqu'au _ray tracing_. Ces 43 | programmes tournent sur de nombreux coeurs simultanément pour y traiter de nombreuses données en parallèle. 44 | 45 | Si vous avez utilisé d'anciens APIs comme OpenGL ou Direct3D, vous êtes habitués à pouvoir changer un quelconque 46 | paramètre de la pipeline à tout moment, avec des fonctions comme `glBlendFunc` ou `OMSSetBlendState`. Cela n'est plus 47 | possible avec Vulkan. La pipeline graphique y est quasiment fixée, et vous devrez en recréer une complètement si 48 | vous voulez changer de shader, y attacher différents framebuffers ou changer le color blending. Devoir créer une 49 | pipeline graphique pour chacune des combinaisons dont vous aurez besoin tout au long du programme représente un gros 50 | travail, mais permet au driver d'optimiser beaucoup mieux l'exécution des tâches car il sait à l'avance ce que la carte 51 | graphique aura à faire. 52 | 53 | Certaines étapes programmables sont optionnelles selon ce que vous voulez faire. Par exemple la tesselation et le 54 | geometry shader peuvent être désactivés. Si vous n'êtes intéressé que par les valeurs de profondeur vous pouvez 55 | désactiver le fragment shader, ce qui est utile pour les [shadow maps](https://en.wikipedia.org/wiki/Shadow_mapping). 56 | 57 | Dans le prochain chapitre nous allons d'abord créer deux étapes nécessaires à l'affichage d'un triangle à l'écran : 58 | le vertex shader et le fragment shader. Les étapes à fonction fixée seront mises en place dans le chapitre suivant. 59 | La dernière préparation nécessaire à la mise en place de la pipeline graphique Vulkan sera de fournir les framebuffers 60 | d'entrée et de sortie. 61 | 62 | Créez la fonction `createGraphicsPipeline` et appelez-la depuis `initVulkan` après `createImageViews`. Nous 63 | travaillerons sur cette fonction dans les chapitres suivants. 64 | 65 | ```c++ 66 | void initVulkan() { 67 | createInstance(); 68 | setupDebugMessenger(); 69 | createSurface(); 70 | pickPhysicalDevice(); 71 | createLogicalDevice(); 72 | createSwapChain(); 73 | createImageViews(); 74 | createGraphicsPipeline(); 75 | } 76 | 77 | ... 78 | 79 | void createGraphicsPipeline() { 80 | 81 | } 82 | ``` 83 | 84 | [Code C++](/code/08_graphics_pipeline.cpp) 85 | -------------------------------------------------------------------------------- /fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/03_Render_pass.md: -------------------------------------------------------------------------------- 1 | ## Préparation 2 | 3 | Avant de finaliser la création de la pipeline nous devons informer Vulkan des attachements des framebuffers utilisés 4 | lors du rendu. Nous devons indiquer combien chaque framebuffer aura de buffers de couleur et de profondeur, combien de 5 | samples il faudra utiliser avec chaque frambuffer et comment les utiliser tout au long des opérations de rendu. Toutes 6 | ces informations sont contenues dans un objet appelé *render pass*. Pour le configurer, créons la fonction 7 | `createRenderPass`. Appelez cette fonction depuis `initVulkan` avant `createGraphicsPipeline`. 8 | 9 | ```c++ 10 | void initVulkan() { 11 | createInstance(); 12 | setupDebugMessenger(); 13 | createSurface(); 14 | pickPhysicalDevice(); 15 | createLogicalDevice(); 16 | createSwapChain(); 17 | createImageViews(); 18 | createRenderPass(); 19 | createGraphicsPipeline(); 20 | } 21 | 22 | ... 23 | 24 | void createRenderPass() { 25 | 26 | } 27 | ``` 28 | 29 | ## Description de l'attachement 30 | 31 | Dans notre cas nous aurons un seul attachement de couleur, et c'est une image de la swap chain. 32 | 33 | ```c++ 34 | void createRenderPass() { 35 | VkAttachmentDescription colorAttachment{}; 36 | colorAttachment.format = swapChainImageFormat; 37 | colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; 38 | } 39 | ``` 40 | 41 | 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 42 | de multisampling pour le moment donc nous devons indiquer que nous n'utilisons qu'un seul sample. 43 | 44 | ```c++ 45 | colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; 46 | colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; 47 | ``` 48 | 49 | Les membres `loadOp` et `storeOp` définissent ce qui doit être fait avec les données de l'attachement respectivement 50 | avant et après le rendu. Pour `loadOp` nous avons les choix suivants : 51 | 52 | * `VK_ATTACHMENT_LOAD_OP_LOAD` : conserve les données présentes dans l'attachement 53 | * `VK_ATTACHMENT_LOAD_OP_CLEAR` : remplace le contenu par une constante 54 | * `VK_ATTACHMENT_LOAD_OP_DONT_CARE` : ce qui existe n'est pas défini et ne nous intéresse pas 55 | 56 | Dans notre cas nous utiliserons l'opération de remplacement pour obtenir un framebuffer noir avant d'afficher une 57 | nouvelle image. Il n'y a que deux possibilités pour le membre `storeOp` : 58 | 59 | * `VK_ATTACHMENT_STORE_OP_STORE` : le rendu est gardé en mémoire et accessible plus tard 60 | * `VK_ATTACHMENT_STORE_OP_DONT_CARE` : le contenu du framebuffer est indéfini dès la fin du rendu 61 | 62 | Nous voulons voir le triangle à l'écran donc nous voulons l'opération de stockage. 63 | 64 | ```c++ 65 | colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; 66 | colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; 67 | ``` 68 | 69 | Les membres `loadOp` et `storeOp` s'appliquent aux données de couleur et de profondeur, et `stencilLoadOp` et 70 | `stencilStoreOp` s'appliquent aux données de stencil. Notre application n'utilisant pas de stencil buffer, nous 71 | pouvons indiquer que les données ne nous intéressent pas. 72 | 73 | ```c++ 74 | colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 75 | colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; 76 | ``` 77 | 78 | Les textures et les framebuffers dans Vulkan sont représentés par des objets de type `VkImage` possédant un certain 79 | format de pixels. Cependant l'organisation des pixels dans la mémoire peut changer selon ce que vous faites de cette 80 | image. 81 | 82 | Les organisations les plus communes sont : 83 | 84 | * `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` : images utilisées comme attachements de couleur 85 | * `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` : images présentées à une swap chain 86 | * `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` : image utilisées comme destination d'opérations de copie de mémoire 87 | 88 | Nous discuterons plus précisément de ce sujet dans le chapitre sur les textures. Ce qui compte pour le moment est que 89 | les images doivent changer d'organisation mémoire selon les opérations qui leur sont appliquées au long de l'exécution 90 | de la pipeline. 91 | 92 | Le membre `initialLayout` spécifie l'organisation de l'image avant le début du rendu. Le membre `finalLayout` fournit 93 | l'organisation vers laquelle l'image doit transitionner à la fin du rendu. La valeur `VK_IMAGE_LAYOUT_UNDEFINED` 94 | 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. 95 | 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 96 | rendre l'image compatible avec la swap chain, nous fournissons `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` pour `finalLayout`. 97 | 98 | ## Subpasses et références aux attachements 99 | 100 | Une unique passe de rendu est composée de plusieurs subpasses. Les subpasses sont des opérations de rendu 101 | dépendant du contenu présent dans le framebuffer quand elles commencent. Elles peuvent consister en des opérations de 102 | post-processing exécutées l'une après l'autre. En regroupant toutes ces opérations en une seule passe, Vulkan peut 103 | alors réaliser des optimisations et conserver de la bande passante pour de potentiellement meilleures performances. 104 | Pour notre triangle nous nous contenterons d'une seule subpasse. 105 | 106 | Chacune d'entre elle référence un ou plusieurs attachements décrits par les structures que nous avons vues 107 | précédemment. Ces références sont elles-mêmes des structures du type `VkAttachmentReference` et ressemblent à cela : 108 | 109 | ```c++ 110 | VkAttachmentReference colorAttachmentRef{}; 111 | colorAttachmentRef.attachment = 0; 112 | colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 113 | ``` 114 | 115 | Le paramètre `attachment` spécifie l'attachement à référencer à l'aide d'un indice correspondant à la position de la 116 | structure dans le tableau de descriptions d'attachements. Notre tableau ne consistera qu'en une seule référence donc 117 | son indice est nécessairement `0`. Le membre `layout` donne l'organisation que l'attachement devrait avoir au début d'une 118 | subpasse utilsant cette référence. Vulkan changera automatiquement l'organisation de l'attachement quand la subpasse 119 | commence. Nous voulons que l'attachement soit un color buffer, et pour cela la meilleure performance sera obtenue avec 120 | `VK_IMAGE_LAYOUT_COLOR_OPTIMAL`, comme son nom le suggère. 121 | 122 | La subpasse est décrite dans la structure `VkSubpassDescription` : 123 | 124 | ```c++ 125 | VkSubpassDescription subpass{}; 126 | subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; 127 | ``` 128 | 129 | Vulkan supportera également des *compute subpasses* donc nous devons indiquer que celle que nous créons est destinée 130 | aux graphismes. Nous spécifions ensuite la référence à l'attachement de couleurs : 131 | 132 | ```c++ 133 | subpass.colorAttachmentCount = 1; 134 | subpass.pColorAttachments = &colorAttachmentRef; 135 | ``` 136 | 137 | L'indice de cet attachement est indiqué dans le fragment shader avec le `location = 0` dans la directive 138 | `layout(location = 0) out vec4 outColor`. 139 | 140 | Les types d'attachements suivants peuvent être indiqués dans une subpasse : 141 | 142 | * `pInputAttachments` : attachements lus depuis un shader 143 | * `pResolveAttachments` : attachements utilisés pour le multisampling d'attachements de couleurs 144 | * `pDepthStencilAttachment` : attachements pour la profondeur et le stencil 145 | * `pPreserveAttachments` : attachements qui ne sont pas utilisés par cette subpasse mais dont les données doivent 146 | être conservées 147 | 148 | ## Passe de rendu 149 | 150 | Maintenant que les attachements et une subpasse simple ont été décrits nous pouvons enfin créer la render pass. 151 | Créez une nouvelle variable du type `VkRenderPass` au-dessus de la variable `pipelineLayout` : 152 | 153 | ```c++ 154 | VkRenderPass renderPass; 155 | VkPipelineLayout pipelineLayout; 156 | ``` 157 | 158 | L'objet représentant la render pass peut alors être créé en remplissant la structure `VkRenderPassCreateInfo` dans 159 | laquelle nous devons remplir un tableau d'attachements et de subpasses. Les objets `VkAttachmentReference` référencent 160 | les attachements en utilisant les indices de ce tableau. 161 | 162 | ```c++ 163 | VkRenderPassCreateInfo renderPassInfo{}; 164 | renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; 165 | renderPassInfo.attachmentCount = 1; 166 | renderPassInfo.pAttachments = &colorAttachment; 167 | renderPassInfo.subpassCount = 1; 168 | renderPassInfo.pSubpasses = &subpass; 169 | 170 | if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { 171 | throw std::runtime_error("échec de la création de la render pass!"); 172 | } 173 | ``` 174 | 175 | Comme l'organisation de la pipeline, nous aurons à utiliser la référence à la passe de rendu tout au long du 176 | programme. Nous devons donc la détruire dans la fonction `cleanup` : 177 | 178 | ```c++ 179 | void cleanup() { 180 | vkDestroyPipelineLayout(device, pipelineLayout, nullptr); 181 | vkDestroyRenderPass(device, renderPass, nullptr); 182 | ... 183 | } 184 | ``` 185 | 186 | Nous avons eu beaucoup de travail, mais nous allons enfin créer la pipeline graphique et l'utiliser dès le prochain 187 | chapitre! 188 | 189 | [Code C++](/code/11_render_passes.cpp) / 190 | [Vertex shader](/code/09_shader_base.vert) / 191 | [Fragment shader](/code/09_shader_base.frag) 192 | -------------------------------------------------------------------------------- /fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/04_Conclusion.md: -------------------------------------------------------------------------------- 1 | Nous pouvons maintenant combiner toutes les structures et tous les objets des chapitres précédentes pour créer la 2 | pipeline graphique! Voici un petit récapitulatif des objets que nous avons : 3 | 4 | * Étapes shader : les modules shader définissent le fonctionnement des étapes programmables de la pipeline graphique 5 | * Étapes à fonction fixée : plusieurs structures paramètrent les étapes à fonction fixée comme l'assemblage des 6 | entrées, le rasterizer, le viewport et le mélange des couleurs 7 | * Organisation de la pipeline : les uniformes et push constants utilisées par les shaders, auxquelles on attribue une 8 | valeur pendant l'exécution de la pipeline 9 | * Render pass : les attachements référencés par la pipeline et leurs utilisations 10 | 11 | Tout cela combiné définit le fonctionnement de la pipeline graphique. Nous pouvons maintenant remplir la structure 12 | `VkGraphicsPipelineCreateInfo` à la fin de la fonction `createGraphicsPipeline`, mais avant les appels à la fonction 13 | `vkDestroyShaderModule` pour ne pas invalider les shaders que la pipeline utilisera. 14 | 15 | Commençons par référencer le tableau de `VkPipelineShaderStageCreateInfo`. 16 | 17 | ```c++ 18 | VkGraphicsPipelineCreateInfo pipelineInfo{}; 19 | pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; 20 | pipelineInfo.stageCount = 2; 21 | pipelineInfo.pStages = shaderStages; 22 | ``` 23 | 24 | Puis donnons toutes les structure décrivant les étapes à fonction fixée. 25 | 26 | ```c++ 27 | pipelineInfo.pVertexInputState = &vertexInputInfo; 28 | pipelineInfo.pInputAssemblyState = &inputAssembly; 29 | pipelineInfo.pViewportState = &viewportState; 30 | pipelineInfo.pRasterizationState = &rasterizer; 31 | pipelineInfo.pMultisampleState = &multisampling; 32 | pipelineInfo.pDepthStencilState = nullptr; // Optionnel 33 | pipelineInfo.pColorBlendState = &colorBlending; 34 | pipelineInfo.pDynamicState = nullptr; // Optionnel 35 | ``` 36 | 37 | Après cela vient l'organisation de la pipeline, qui est une référence à un objet Vulkan plutôt qu'une structure. 38 | 39 | ```c++ 40 | pipelineInfo.layout = pipelineLayout; 41 | ``` 42 | 43 | Finalement nous devons fournir les références à la render pass et aux indices des subpasses. Il est aussi possible 44 | d'utiliser d'autres render passes avec cette pipeline mais elles doivent être compatibles avec `renderPass`. La 45 | signification de compatible est donnée 46 | [ici](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility), mais nous 47 | n'utiliserons pas cette possibilité dans ce tutoriel. 48 | 49 | ```c++ 50 | pipelineInfo.renderPass = renderPass; 51 | pipelineInfo.subpass = 0; 52 | ``` 53 | 54 | Il nous reste en fait deux paramètres : `basePipelineHandle` et `basePipelineIndex`. Vulkan vous permet de créer une 55 | nouvelle pipeline en "héritant" d'une pipeline déjà existante. L'idée derrière cette fonctionnalité est qu'il 56 | est moins coûteux de créer une pipeline à partir d'une qui existe déjà, mais surtout que passer d'une pipeline à une 57 | autre est plus rapide si elles ont un même parent. Vous pouvez spécifier une pipeline de deux manières : soit en 58 | fournissant une référence soit en donnant l'indice de la pipeline à hériter. Nous n'utilisons pas cela donc 59 | 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 60 | `flags` de la structure `VkGraphicsPipelineCreateInfo` comporte `VK_PIPELINE_CREATE_DERIVATIVE_BIT`. 61 | 62 | ```c++ 63 | pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optionnel 64 | pipelineInfo.basePipelineIndex = -1; // Optionnel 65 | ``` 66 | 67 | Préparons-nous pour l'étape finale en créant un membre donnée où stocker la référence à la `VkPipeline` : 68 | 69 | ```c++ 70 | VkPipeline graphicsPipeline; 71 | ``` 72 | 73 | Et créons enfin la pipeline graphique : 74 | 75 | ```c++ 76 | if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { 77 | throw std::runtime_error("échec de la création de la pipeline graphique!"); 78 | } 79 | ``` 80 | 81 | La fonction `vkCreateGraphicsPipelines` possède en fait plus de paramètres que les fonctions de création d'objet que 82 | nous avons pu voir jusqu'à présent. Elle peut en effet accepter plusieurs structures `VkGraphicsPipelineCreateInfo` 83 | et créer plusieurs `VkPipeline` en un seul appel. 84 | 85 | Le second paramètre que nous n'utilisons pas ici (mais que nous reverrons dans un chapitre qui lui sera dédié) sert à 86 | fournir un objet `VkPipelineCache` optionnel. Un tel objet peut être stocké et réutilisé entre plusieurs appels de la 87 | fonction et même entre plusieurs exécutions du programme si son contenu est correctement stocké dans un fichier. Cela 88 | permet de grandement accélérer la création des pipelines. 89 | 90 | La pipeline graphique est nécessaire à toutes les opérations d'affichage, nous ne devrons donc la supprimer qu'à la fin 91 | du programme dans la fonction `cleanup` : 92 | 93 | ```c++ 94 | void cleanup() { 95 | vkDestroyPipeline(device, graphicsPipeline, nullptr); 96 | vkDestroyPipelineLayout(device, pipelineLayout, nullptr); 97 | ... 98 | } 99 | ``` 100 | 101 | Exécutez votre programme pour vérifier que tout ce travail a enfin résulté dans la création d'une pipeline graphique. 102 | Nous sommes de plus en plus proches d'avoir un dessin à l'écran! Dans les prochains chapitres nous générerons les 103 | framebuffers à partir des images de la swap chain et préparerons les commandes d'affichage. 104 | 105 | [Code C++](/code/12_graphics_pipeline_complete.cpp) / 106 | [Vertex shader](/code/09_shader_base.vert) / 107 | [Fragment shader](/code/09_shader_base.frag) 108 | -------------------------------------------------------------------------------- /fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/00_Framebuffers.md: -------------------------------------------------------------------------------- 1 | Nous avons beaucoup parlé de framebuffers dans les chapitres précédents, et nous avons mis en place la render pass 2 | 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. 3 | 4 | Les attachements de différents types spécifiés durant la render pass sont liés en les considérant dans des objets de 5 | type `VkFramebuffer`. Un tel objet référence toutes les `VkImageView` utilisées comme attachements par une passe. 6 | Dans notre cas nous n'en aurons qu'un : un attachement de couleur, qui servira de cible d'affichage uniquement. 7 | Cependant l'image utilisée dépendra de l'image fournie par la swap chain lors de la requête pour l'affichage. Nous 8 | devons donc créer un framebuffer pour chacune des images de la swap chain et utiliser le bon au moment de l'affichage. 9 | 10 | Pour cela créez un autre `std::vector` qui contiendra des framebuffers : 11 | 12 | ```c++ 13 | std::vector swapChainFramebuffers; 14 | ``` 15 | 16 | Nous allons remplir ce `vector` depuis une nouvelle fonction `createFramebuffers` que nous appellerons depuis 17 | `initVulkan` juste après la création de la pipeline graphique : 18 | 19 | ```c++ 20 | void initVulkan() { 21 | createInstance(); 22 | setupDebugMessenger(); 23 | createSurface(); 24 | pickPhysicalDevice(); 25 | createLogicalDevice(); 26 | createSwapChain(); 27 | createImageViews(); 28 | createRenderPass(); 29 | createGraphicsPipeline(); 30 | createFramebuffers(); 31 | } 32 | 33 | ... 34 | 35 | void createFramebuffers() { 36 | 37 | } 38 | ``` 39 | 40 | Commencez par redimensionner le conteneur afin qu'il puisse stocker tous les framebuffers : 41 | 42 | ```c++ 43 | void createFramebuffers() { 44 | swapChainFramebuffers.resize(swapChainImageViews.size()); 45 | } 46 | ``` 47 | 48 | Nous allons maintenant itérer à travers toutes les images et créer un framebuffer à partir de chacune d'entre elles : 49 | 50 | ```c++ 51 | for (size_t i = 0; i < swapChainImageViews.size(); i++) { 52 | VkImageView attachments[] = { 53 | swapChainImageViews[i] 54 | }; 55 | 56 | VkFramebufferCreateInfo framebufferInfo{}; 57 | framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; 58 | framebufferInfo.renderPass = renderPass; 59 | framebufferInfo.attachmentCount = 1; 60 | framebufferInfo.pAttachments = attachments; 61 | framebufferInfo.width = swapChainExtent.width; 62 | framebufferInfo.height = swapChainExtent.height; 63 | framebufferInfo.layers = 1; 64 | 65 | if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { 66 | throw std::runtime_error("échec de la création d'un framebuffer!"); 67 | } 68 | } 69 | ``` 70 | 71 | Comme vous le pouvez le voir la création d'un framebuffer est assez simple. Nous devons d'abord indiquer avec quelle 72 | `renderPass` le framebuffer doit être compatible. Sachez que si vous voulez utiliser un framebuffer avec plusieurs 73 | render passes, les render passes spécifiées doivent être compatibles entre elles. La compatibilité signifie ici 74 | approximativement qu'elles utilisent le même nombre d'attachements du même type. Ceci implique qu'il ne faut pas 75 | s'attendre à ce qu'une render pass puisse ignorer certains attachements d'un framebuffer qui en aurait trop. 76 | 77 | Les paramètres `attachementCount` et `pAttachments` doivent donner la taille du tableau contenant les `VkImageViews` 78 | qui servent d'attachements. 79 | 80 | Les paramètres `width` et `height` sont évidents. Le membre `layers` correspond au nombres de couches dans les images 81 | fournies comme attachements. Les images de la swap chain n'ont toujours qu'une seule couche donc nous indiquons `1`. 82 | 83 | Nous devons détruire les framebuffers avant les image views et la render pass dans la fonction `cleanup` : 84 | 85 | ```c++ 86 | void cleanup() { 87 | for (auto framebuffer : swapChainFramebuffers) { 88 | vkDestroyFramebuffer(device, framebuffer, nullptr); 89 | } 90 | 91 | ... 92 | } 93 | ``` 94 | 95 | Nous avons atteint le moment où tous les objets sont prêts pour l'affichage. Dans le prochain chapitre nous allons 96 | écrire les commandes d'affichage. 97 | 98 | [Code C++](/code/13_framebuffers.cpp) / 99 | [Vertex shader](/code/09_shader_base.vert) / 100 | [Fragment shader](/code/09_shader_base.frag) 101 | -------------------------------------------------------------------------------- /fr/04_Vertex_buffers/03_Index_buffer.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Les modèles 3D que vous serez susceptibles d'utiliser dans des applications réelles partagerons le plus souvent des 4 | vertices communs à plusieurs triangles. Cela est d'ailleurs le cas avec un simple rectangle : 5 | 6 | ![](/images/vertex_vs_index.svg) 7 | 8 | Un rectangle est composé de triangles, ce qui signifie que nous aurions besoin d'un vertex buffer avec 6 vertices. Mais 9 | nous dupliquerions alors des vertices, aboutissant à un gachis de mémoire. Dans des modèles plus complexes, les vertices 10 | sont en moyenne en contact avec 3 triangles, ce qui serait encore pire. La solution consiste à utiliser un index buffer. 11 | 12 | Un index buffer est essentiellement un tableau de références vers le vertex buffer. Il vous permet de réordonner ou de 13 | dupliquer les données de ce buffer. L'image ci-dessus démontre l'utilité de cette méthode. 14 | 15 | ## Création d'un index buffer 16 | 17 | Dans ce chapitre, nous allons ajouter les données nécessaires à l'affichage d'un rectangle. Nous allons ainsi rajouter 18 | une coordonnée dans le vertex buffer et créer un index buffer. Voici les données des sommets au complet : 19 | 20 | ```c++ 21 | const std::vector vertices = { 22 | {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, 23 | {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, 24 | {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, 25 | {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} 26 | }; 27 | ``` 28 | 29 | Le coin en haut à gauche est rouge, celui en haut à droite est vert, celui en bas à droite est bleu et celui en bas à 30 | gauche est blanc. Les couleurs seront dégradées par l'interpolation du rasterizer. Nous allons maintenant créer le 31 | tableau `indices` pour représenter l'index buffer. Son contenu correspond à ce qui est présenté dans l'illustration. 32 | 33 | ```c++ 34 | const std::vector indices = { 35 | 0, 1, 2, 2, 3, 0 36 | }; 37 | ``` 38 | 39 | Il est possible d'utiliser `uint16_t` ou `uint32_t` pour les valeurs de l'index buffer, en fonction du nombre d'éléments 40 | dans `vertices`. Nous pouvons nous contenter de `uint16_t` car nous n'utilisons pas plus de 65535 sommets différents. 41 | 42 | Comme les données des sommets, nous devons placer les indices dans un `VkBuffer` pour que le GPU puisse y avoir accès. 43 | Créez deux membres donnée pour référencer les ressources du futur index buffer : 44 | 45 | ```c++ 46 | VkBuffer vertexBuffer; 47 | VkDeviceMemory vertexBufferMemory; 48 | VkBuffer indexBuffer; 49 | VkDeviceMemory indexBufferMemory; 50 | ``` 51 | 52 | La fonction `createIndexBuffer` est quasiment identique à `createVertexBuffer` : 53 | 54 | ```c++ 55 | void initVulkan() { 56 | ... 57 | createVertexBuffer(); 58 | createIndexBuffer(); 59 | ... 60 | } 61 | 62 | void createIndexBuffer() { 63 | VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); 64 | 65 | VkBuffer stagingBuffer; 66 | VkDeviceMemory stagingBufferMemory; 67 | createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); 68 | 69 | void* data; 70 | vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); 71 | memcpy(data, indices.data(), (size_t) bufferSize); 72 | vkUnmapMemory(device, stagingBufferMemory); 73 | 74 | createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); 75 | 76 | copyBuffer(stagingBuffer, indexBuffer, bufferSize); 77 | 78 | vkDestroyBuffer(device, stagingBuffer, nullptr); 79 | vkFreeMemory(device, stagingBufferMemory, nullptr); 80 | } 81 | ``` 82 | 83 | Il n'y a que deux différences : `bufferSize` correspond à la taille du tableau multiplié par `sizeof(uint16_t)`, et 84 | `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT` est remplacé par `VK_BUFFER_USAGE_INDEX_BUFFER_BIT`. À part ça tout est 85 | identique : nous créons un buffer intermédiaire puis le copions dans le buffer final local au GPU. 86 | 87 | L'index buffer doit être libéré à la fin du programme depuis `cleanup`. 88 | 89 | ```c++ 90 | void cleanup() { 91 | cleanupSwapChain(); 92 | 93 | vkDestroyBuffer(device, indexBuffer, nullptr); 94 | vkFreeMemory(device, indexBufferMemory, nullptr); 95 | 96 | vkDestroyBuffer(device, vertexBuffer, nullptr); 97 | vkFreeMemory(device, vertexBufferMemory, nullptr); 98 | 99 | ... 100 | } 101 | ``` 102 | 103 | ## Utilisation d'un index buffer 104 | 105 | Pour utiliser l'index buffer lors des opérations de rendu nous devons modifier un petit peu `createCommandBuffers`. Tout 106 | 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 107 | plus il n'est pas possible de subdiviser les sommets en leurs coordonnées, ce qui implique que la modification d'une 108 | seule coordonnée nécessite de créer un autre sommet le vertex buffer. 109 | 110 | ```c++ 111 | vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); 112 | 113 | vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); 114 | ``` 115 | 116 | Un index buffer est lié par la fonction `vkCmdBindIndexBuffer`. Elle prend en paramètres le buffer, le décalage dans ce 117 | buffer et le type de donnée. Pour nous ce dernier sera `VK_INDEX_TYPE_UINT16`. 118 | 119 | Simplement lier le vertex buffer ne change en fait rien. Il nous faut aussi mettre à jour les commandes d'affichage 120 | pour indiquer à Vulkan comment utiliser le buffer. Supprimez l'appel à `vkCmdDraw`, et remplacez-le par 121 | `vkCmdDrawIndexed` : 122 | 123 | ```c++ 124 | vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); 125 | ``` 126 | 127 | Le deuxième paramètre indique le nombre d'indices. Le troisième est le nombre d'instances à invoquer (ici `1` car nous 128 | n'utilisons par cette technique). Le paramètre suivant est un décalage dans l'index buffer, sachant qu'ici il ne 129 | fonctionne pas en octets mais en indices. L'avant-dernier paramètre permet de fournir une valeur qui sera ajoutée à tous 130 | les indices juste avant de les faire correspondre aux vertices. Enfin, le dernier paramètre est un décalage pour le 131 | rendu instancié. 132 | 133 | Lancez le programme et vous devriez avoir ceci : 134 | 135 | ![](/images/indexed_rectangle.png) 136 | 137 | Vous savez maintenant économiser la mémoire en réutilisant les vertices à l'aide d'un index buffer. Cela deviendra 138 | crucial pour les chapitres suivants dans lesquels vous allez apprendre à charger des modèles complexes. 139 | 140 | Nous avons déjà évoqué le fait que le plus de buffers possibles devraient être stockés dans un seul emplacement 141 | mémoire. Il faudrait dans l'idéal allez encore plus loin : 142 | [les développeurs des drivers recommandent](https://developer.nvidia.com/vulkan-memory-management) également que vous 143 | placiez plusieurs buffers dans un seul et même `VkBuffer`, et que vous utilisiez des décalages pour les différencier 144 | dans les fonctions comme `vkCmdBindVertexBuffers`. Cela simplifie la mise des données dans des caches car elles sont 145 | regroupées en un bloc. Il devient même possible d'utiliser la même mémoire pour plusieurs ressources si elles ne sont 146 | pas utilisées en même temps et si elles sont proprement mises à jour. Cette pratique s'appelle d'ailleurs *aliasing*, et 147 | certaines fonctions Vulkan possèdent un paramètre qui permet au développeur d'indiquer s'il veut utiliser la technique. 148 | 149 | [Code C++](/code/20_index_buffer.cpp) / 150 | [Vertex shader](/code/17_shader_vertexbuffer.vert) / 151 | [Fragment shader](/code/17_shader_vertexbuffer.frag) 152 | -------------------------------------------------------------------------------- /fr/90_FAQ.md: -------------------------------------------------------------------------------- 1 | Cette page liste quelques problèmes que vous pourriez rencontrer lors du développement d'une application Vulkan. 2 | 3 | * **J'obtiens un erreur de violation d'accès dans les validations layers** : assurez-vous que MSI Afterburner / 4 | RivaTuner Statistics Server ne tournent pas, car ils possèdent des problèmes de compatibilité avec Vulkan. 5 | 6 | * **Je ne vois aucun message provenant des validation layers / les validation layers ne sont pas disponibles** : 7 | assurez-vous d'abord que les validation layers peuvent écrire leurs message en laissant le terminal ouvert après 8 | l'exécution. Avec Visual Studio, lancez le programme avec Ctrl-F5. Sous Linux, lancez le programme depuis un terminal. 9 | 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"). 10 | Assurez-vous également que le SDK est au moins de la version 1.1.106.0 pour le support de `VK_LAYER_KHRONOS_validation`. 11 | 12 | * **vkCreateSwapchainKHR induit une erreur dans SteamOverlayVulkanLayer64.dll** : Il semble qu'il y ait un problème de 13 | compatibilité avec la version beta du client Steam. Il y a quelques moyens de régler le conflit : 14 | * Désinstaller Steam 15 | * Mettre la variable d'environnement `DISABLE_VK_LAYER_VALVE_steam_overlay_1` à `1` 16 | * Supprimer la layer de Steam dans le répertoire sous `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers` 17 | 18 | Exemple pour la variable : 19 | 20 | ![](/images/steam_layers_env.png) -------------------------------------------------------------------------------- /fr/95_Politique_de_confidentialité.md: -------------------------------------------------------------------------------- 1 | ## Généralités 2 | 3 | Cette politique de confidentialité concerne les informations collectées quand vous utilisez vulkan-tutorial.com ou 4 | l'un de ses sous-domaines. Il décrit la manière dont Alexander Overvoorde, propriétaire du site, collecte, utilise et 5 | partage les informations vous concernant. 6 | 7 | ## Renseignements 8 | 9 | Ce site web collecte des informations sur ses visiteurs à l'aide d'une instance Matomo 10 | ([https://matomo.org/](https://matomo.org/)) localement hébergée. Il analyse les pages que vous visitez, le type 11 | d'appareil et le navigateur que vous utilisez, combien de temps vous restez sur une page et comment vous êtes arrivés 12 | sur le site. Ces informations sont anonymisées en ne stockant que les deux premiers octets de votre addresse IP (par 13 | exemple `123.123.xxx.xxx`). Ces données sont stockés pour une durée indéterminée. 14 | 15 | Les données sont utilisées dans déterminer trois données : la manière dont le site est utilisé, le nombre de visiteurs 16 | 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 17 | déterminer les zones du site à améliorer. Par exemple cela permet de savoir s'il faut investir plus de temps dans 18 | l'interface mobile. 19 | 20 | Ces données ne sont pas partagées à des tiers. 21 | 22 | ## Publicité 23 | 24 | Ce site utilise des publicités fournies par des serveurs tiers. Elles peuvent utiliser des cookies pour suivre 25 | l'activité du site ou l'engagement avec les publicités. 26 | 27 | ## Commentaires 28 | 29 | Chaque chapitre comporte une section commentaires. Elle utilise le service tier Disqus. Ce service collecte des données 30 | individuelles pour faciliter la lecture et l'émission de commentaires. Il agglomère ces informations pour améliorer son 31 | offre de services. 32 | 33 | La politique de confidentialité complète de ce service tier se trouve à l'addresse suivante : 34 | [https://help.disqus.com/terms-and-policies/disqus-privacy-policy](https://help.disqus.com/terms-and-policies/disqus-privacy-policy). -------------------------------------------------------------------------------- /images/aliasing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/aliasing.png -------------------------------------------------------------------------------- /images/anisotropic_filtering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/anisotropic_filtering.png -------------------------------------------------------------------------------- /images/antialiasing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/antialiasing.png -------------------------------------------------------------------------------- /images/compute_shader_particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/compute_shader_particles.png -------------------------------------------------------------------------------- /images/cube_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/cube_demo.png -------------------------------------------------------------------------------- /images/cube_demo_mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/cube_demo_mac.png -------------------------------------------------------------------------------- /images/cube_demo_nowindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/cube_demo_nowindow.png -------------------------------------------------------------------------------- /images/depth_correct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/depth_correct.png -------------------------------------------------------------------------------- /images/depth_issues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/depth_issues.png -------------------------------------------------------------------------------- /images/drawing_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/drawing_model.png -------------------------------------------------------------------------------- /images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/favicon.png -------------------------------------------------------------------------------- /images/glfw_directory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/glfw_directory.png -------------------------------------------------------------------------------- /images/highmipmaps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/highmipmaps.png -------------------------------------------------------------------------------- /images/include_dirs_stb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/include_dirs_stb.png -------------------------------------------------------------------------------- /images/include_dirs_tinyobjloader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/include_dirs_tinyobjloader.png -------------------------------------------------------------------------------- /images/indexed_rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/indexed_rectangle.png -------------------------------------------------------------------------------- /images/inverted_texture_coordinates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/inverted_texture_coordinates.png -------------------------------------------------------------------------------- /images/library_directory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/library_directory.png -------------------------------------------------------------------------------- /images/mipmaps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/mipmaps.png -------------------------------------------------------------------------------- /images/mipmaps_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/mipmaps_comparison.png -------------------------------------------------------------------------------- /images/mipmaps_example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/mipmaps_example.jpg -------------------------------------------------------------------------------- /images/multisampling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/multisampling.png -------------------------------------------------------------------------------- /images/multisampling_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/multisampling_comparison.png -------------------------------------------------------------------------------- /images/multisampling_comparison2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/multisampling_comparison2.png -------------------------------------------------------------------------------- /images/sample_shading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/sample_shading.png -------------------------------------------------------------------------------- /images/select_develop_branch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/select_develop_branch.png -------------------------------------------------------------------------------- /images/semaphore_in_use.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/semaphore_in_use.png -------------------------------------------------------------------------------- /images/spinning_quad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/spinning_quad.png -------------------------------------------------------------------------------- /images/steam_layers_env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/steam_layers_env.png -------------------------------------------------------------------------------- /images/swap_chain_validation_layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/swap_chain_validation_layer.png -------------------------------------------------------------------------------- /images/texcoord_visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/texcoord_visualization.png -------------------------------------------------------------------------------- /images/texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/texture.jpg -------------------------------------------------------------------------------- /images/texture_addressing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/texture_addressing.png -------------------------------------------------------------------------------- /images/texture_filtering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/texture_filtering.png -------------------------------------------------------------------------------- /images/texture_on_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/texture_on_square.png -------------------------------------------------------------------------------- /images/texture_on_square_colorized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/texture_on_square_colorized.png -------------------------------------------------------------------------------- /images/texture_on_square_repeated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/texture_on_square_repeated.png -------------------------------------------------------------------------------- /images/triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/triangle.png -------------------------------------------------------------------------------- /images/triangle_coordinates.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 67 | 73 | v0 84 | v1 95 | v2 106 | 107 | 108 | -------------------------------------------------------------------------------- /images/triangle_coordinates_colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/triangle_coordinates_colors.png -------------------------------------------------------------------------------- /images/triangle_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/triangle_white.png -------------------------------------------------------------------------------- /images/validation_layer_anisotropy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/validation_layer_anisotropy.png -------------------------------------------------------------------------------- /images/validation_layer_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/validation_layer_test.png -------------------------------------------------------------------------------- /images/viewports_scissors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/viewports_scissors.png -------------------------------------------------------------------------------- /images/vs_all_configs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_all_configs.png -------------------------------------------------------------------------------- /images/vs_application_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_application_settings.png -------------------------------------------------------------------------------- /images/vs_build_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_build_mode.png -------------------------------------------------------------------------------- /images/vs_cpp17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_cpp17.png -------------------------------------------------------------------------------- /images/vs_cpp_general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_cpp_general.png -------------------------------------------------------------------------------- /images/vs_dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_dependencies.png -------------------------------------------------------------------------------- /images/vs_export_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_export_template.png -------------------------------------------------------------------------------- /images/vs_include_dirs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_include_dirs.png -------------------------------------------------------------------------------- /images/vs_link_dirs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_link_dirs.png -------------------------------------------------------------------------------- /images/vs_link_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_link_input.png -------------------------------------------------------------------------------- /images/vs_link_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_link_settings.png -------------------------------------------------------------------------------- /images/vs_new_cpp_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_new_cpp_project.png -------------------------------------------------------------------------------- /images/vs_new_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_new_item.png -------------------------------------------------------------------------------- /images/vs_new_source_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_new_source_file.png -------------------------------------------------------------------------------- /images/vs_open_project_properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_open_project_properties.png -------------------------------------------------------------------------------- /images/vs_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_template.png -------------------------------------------------------------------------------- /images/vs_test_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vs_test_window.png -------------------------------------------------------------------------------- /images/vulkan_pipeline_block_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vulkan_pipeline_block_diagram.png -------------------------------------------------------------------------------- /images/vulkan_sdk_download_buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/vulkan_sdk_download_buttons.png -------------------------------------------------------------------------------- /images/xcode_frameworks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/xcode_frameworks.png -------------------------------------------------------------------------------- /images/xcode_new_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/xcode_new_project.png -------------------------------------------------------------------------------- /images/xcode_new_project_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/xcode_new_project_2.png -------------------------------------------------------------------------------- /images/xcode_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/xcode_output.png -------------------------------------------------------------------------------- /images/xcode_paths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/xcode_paths.png -------------------------------------------------------------------------------- /images/xcode_variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/images/xcode_variables.png -------------------------------------------------------------------------------- /resources/viking_room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Overv/VulkanTutorial/02e283d54835aa0c4aed2cdc361baf10335137a6/resources/viking_room.png --------------------------------------------------------------------------------