├── .github └── workflows │ └── mdbook.yml ├── .gitignore ├── CONTRIBUTING.txt ├── Cargo.toml ├── LICENSE ├── README.adoc ├── TERMINOLOGY.csv ├── book ├── book.toml ├── preprocessor │ ├── Cargo.toml │ ├── build.rs │ └── main.rs ├── src │ ├── SUMMARY.md │ ├── conclusion.md │ ├── development_environment.md │ ├── drawing │ │ ├── command_buffers.md │ │ ├── framebuffers.md │ │ └── rendering_and_presentation.md │ ├── dynamic │ │ ├── push_constants.md │ │ ├── recycling_command_buffers.md │ │ └── secondary_command_buffers.md │ ├── faq.md │ ├── images │ │ ├── 3_models.png │ │ ├── 4_models.png │ │ ├── aliasing.png │ │ ├── anisotropic_filtering.png │ │ ├── antialiasing.png │ │ ├── cube_demo.png │ │ ├── cube_demo_mac.png │ │ ├── cube_demo_nowindow.png │ │ ├── depth_correct.png │ │ ├── depth_issues.png │ │ ├── drawing_model.png │ │ ├── extra_square.svg │ │ ├── highmipmaps.png │ │ ├── i_have_no_idea_what_im_doing.jpg │ │ ├── indexed_rectangle.png │ │ ├── inverted_texture_coordinates.png │ │ ├── mipmaps.png │ │ ├── mipmaps_comparison.png │ │ ├── mipmaps_comparison_axe.png │ │ ├── mipmaps_example.jpg │ │ ├── multisampling.png │ │ ├── multisampling_comparison.png │ │ ├── multisampling_comparison_axe.png │ │ ├── normalized_device_coordinates.svg │ │ ├── opacity_push_constant.png │ │ ├── sample_shading.png │ │ ├── semaphore_in_use.png │ │ ├── spinning_ghost_model.png │ │ ├── spinning_quad.png │ │ ├── steam_layers_env.png │ │ ├── swapchain_validation_layer.png │ │ ├── texcoord_visualization.png │ │ ├── texture.png │ │ ├── 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 │ │ ├── viking_room.obj │ │ ├── viking_room.png │ │ ├── vulkan_sdk_download_buttons.png │ │ └── vulkan_simplified_pipeline.svg │ ├── introduction.md │ ├── model │ │ ├── depth_buffering.md │ │ └── loading_models.md │ ├── overview.md │ ├── pipeline │ │ ├── conclusion.md │ │ ├── fixed_functions.md │ │ ├── introduction.md │ │ ├── render_passes.md │ │ └── shader_modules.md │ ├── presentation │ │ ├── image_views.md │ │ ├── swapchain.md │ │ └── window_surface.md │ ├── quality │ │ ├── generating_mipmaps.md │ │ └── multisampling.md │ ├── setup │ │ ├── base_code.md │ │ ├── instance.md │ │ ├── logical_device_and_queues.md │ │ ├── physical_devices_and_queue_families.md │ │ └── validation_layers.md │ ├── swapchain │ │ └── recreation.md │ ├── texture │ │ ├── combined_image_sampler.md │ │ ├── image_view_and_sampler.md │ │ └── images.md │ ├── uniform │ │ ├── descriptor_pool_and_sets.md │ │ └── descriptor_set_layout_and_buffer.md │ └── vertex │ │ ├── index_buffer.md │ │ ├── staging_buffer.md │ │ ├── vertex_buffer_creation.md │ │ └── vertex_input_description.md ├── vulkan-concepts.drawio └── vulkan-concepts.png ├── resources ├── texture.png ├── viking_room.obj └── viking_room.png ├── rustfmt.toml ├── shaders ├── 17 │ ├── frag.spv │ ├── shader.frag │ ├── shader.vert │ └── vert.spv ├── 21 │ ├── frag.spv │ ├── shader.frag │ ├── shader.vert │ └── vert.spv ├── 25 │ ├── frag.spv │ ├── shader.frag │ ├── shader.vert │ └── vert.spv ├── 26 │ ├── frag.spv │ ├── shader.frag │ ├── shader.vert │ └── vert.spv ├── 30 │ ├── frag.spv │ ├── shader.frag │ ├── shader.vert │ └── vert.spv └── 09 │ ├── frag.spv │ ├── shader.frag │ ├── shader.vert │ └── vert.spv ├── site ├── googlea55b23224d999983.html └── sitemap.xml └── src ├── 00_base_code.rs ├── 01_instance_creation.rs ├── 02_validation_layers.rs ├── 03_physical_device_selection.rs ├── 04_logical_device.rs ├── 05_window_surface.rs ├── 06_swapchain_creation.rs ├── 07_image_views.rs ├── 08_graphics_pipeline.rs ├── 09_shader_modules.rs ├── 10_fixed_functions.rs ├── 11_render_passes.rs ├── 12_graphics_pipeline_complete.rs ├── 13_framebuffers.rs ├── 14_command_buffers.rs ├── 15_hello_triangle.rs ├── 16_swapchain_recreation.rs ├── 17_vertex_input.rs ├── 18_vertex_buffer.rs ├── 19_staging_buffer.rs ├── 20_index_buffer.rs ├── 21_descriptor_set_layout.rs ├── 22_descriptor_sets.rs ├── 23_texture_image.rs ├── 24_sampler.rs ├── 25_texture_mapping.rs ├── 26_depth_buffering.rs ├── 27_model_loading.rs ├── 28_mipmapping.rs ├── 29_multisampling.rs ├── 30_push_constants.rs ├── 31_recycling_command_buffers.rs ├── 32_secondary_command_buffers.rs ├── manage.py ├── patch.ps1 ├── patch.sh ├── patch.zsh └── scripts ├── patch.ps1 ├── patch.sh └── patch.zsh /.github/workflows/mdbook.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a mdBook site to GitHub Pages 2 | # 3 | # To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html 4 | # 5 | name: Deploy mdBook site to Pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ["master"] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 23 | concurrency: 24 | group: "pages" 25 | cancel-in-progress: false 26 | 27 | jobs: 28 | # Build job 29 | build: 30 | runs-on: ubuntu-latest 31 | env: 32 | MDBOOK_VERSION: 0.4.21 33 | steps: 34 | - uses: actions/checkout@v3 35 | - name: Install mdBook 36 | run: | 37 | mkdir bin 38 | curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.32/mdbook-v0.4.32-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin 39 | working-directory: ./book 40 | - name: Setup Pages 41 | id: pages 42 | uses: actions/configure-pages@v3 43 | - name: Build with mdBook 44 | run: bin/mdbook build 45 | working-directory: ./book 46 | - name: Copy Google verification file to build directory 47 | run: cp ./site/googlea55b23224d999983.html ./book/book 48 | - name: Copy site.xml to build directory 49 | run: cp ./site/sitemap.xml ./book/book 50 | - name: Upload artifact 51 | uses: actions/upload-pages-artifact@v2 52 | with: 53 | path: ./book/book 54 | 55 | # Deployment job 56 | deploy: 57 | environment: 58 | name: github-pages 59 | url: ${{ steps.deployment.outputs.page_url }} 60 | runs-on: ubuntu-latest 61 | needs: build 62 | steps: 63 | - name: Deploy to GitHub Pages 64 | id: deployment 65 | uses: actions/deploy-pages@v2 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | book/book 3 | 4 | target 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.txt: -------------------------------------------------------------------------------- 1 | 如何贡献 2 | 1. 在 issue #1 中选择想要翻译的章节 3 | 2. 将任务转换为 issue 并分配给自己 4 | 3. 创建一个分支并开始翻译 5 | 4. 翻译完成后,创建一个 pull request 并将其关联到 issue 6 | 7 | 翻译指引 8 | 1. 不用介意在文章中使用“你”或者“我们”,原文怎么写我们就怎么翻。移除 9 | “主观色彩”往往需要大规模地调整语序,翻译和校对工作就做不完了。 10 | 2. 专有名词可先查 TERMINOLOGY.csv,如果其中没有,可以参考 Milo Yip 给出的翻译, 11 | Vulkan Tutorial CN 的翻译以及 LearnOpenGL CN 的翻译;最后询问 Chuigda 12 | 3. 第一次遇到专有名词时,应在括号里加上英文原文,例如“渲染(rendering)”, 13 | 然后将其添加到 TERMINOLOGY.csv 中 14 | 4. 因为中文没有 that 从句,如果遇到从句,直接翻译的话会产生一个很长的定语, 15 | 这时可以考虑拆分出多个简单句 16 | 5. 翻译时,在每个页面最开头加上原文链接和翻译本文时的提交哈希 17 | 本仓库基于 ceb4a3fc6d8ca565af4f8679c4889bcad7941338 分叉,目前都先填这个 18 | 6. 每个页面最开头的 **Code** 翻译成“本章代码” 19 | 7. 翻译源码中的注释,但是不要翻译源码中的字符串,因为如果我们这么做,就得 20 | 连带本教程附带的、每一章的完整源码一块翻译。那个可能留到以后再做吧。 21 | 8. 从不说“您” 22 | 23 | -- 24 | 25 | 临时策略 26 | 1. 禁止将 ChatGPT 用作翻译 (GitHub Copilot 允许使用),因为我们发现 ChatGPT 27 | 经常搞错原文的语义,在某些时候甚至不如传统的翻译软件可靠 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | publish = false 4 | 5 | edition = "2021" 6 | 7 | name = "tutorial" 8 | authors = ["Kyle Mayes "] 9 | 10 | version = "0.1.0" 11 | 12 | readme = "../README.md" 13 | license = "Apache-2.0" 14 | 15 | description = "Tutorial." 16 | 17 | [dependencies] 18 | 19 | anyhow = "1" 20 | log = "0.4" 21 | cgmath = "0.18" 22 | png = "0.17" 23 | pretty_env_logger = "0.5" 24 | thiserror = "1" 25 | tobj = { version = "3", features = ["log"] } 26 | vulkanalia = { version = "=0.25.0", features = ["libloading", "provisional", "window"] } 27 | winit = "0.29" 28 | 29 | [[bin]] 30 | 31 | name = "00_base_code" 32 | path = "src/00_base_code.rs" 33 | 34 | [[bin]] 35 | 36 | name = "01_instance_creation" 37 | path = "src/01_instance_creation.rs" 38 | 39 | [[bin]] 40 | 41 | name = "02_validation_layers" 42 | path = "src/02_validation_layers.rs" 43 | 44 | [[bin]] 45 | 46 | name = "03_physical_device_selection" 47 | path = "src/03_physical_device_selection.rs" 48 | 49 | [[bin]] 50 | 51 | name = "04_logical_device" 52 | path = "src/04_logical_device.rs" 53 | 54 | [[bin]] 55 | 56 | name = "05_window_surface" 57 | path = "src/05_window_surface.rs" 58 | 59 | [[bin]] 60 | 61 | name = "06_swapchain_creation" 62 | path = "src/06_swapchain_creation.rs" 63 | 64 | [[bin]] 65 | 66 | name = "07_image_views" 67 | path = "src/07_image_views.rs" 68 | 69 | [[bin]] 70 | 71 | name = "08_graphics_pipeline" 72 | path = "src/08_graphics_pipeline.rs" 73 | 74 | [[bin]] 75 | 76 | name = "09_shader_modules" 77 | path = "src/09_shader_modules.rs" 78 | 79 | [[bin]] 80 | 81 | name = "10_fixed_functions" 82 | path = "src/10_fixed_functions.rs" 83 | 84 | [[bin]] 85 | 86 | name = "11_render_passes" 87 | path = "src/11_render_passes.rs" 88 | 89 | [[bin]] 90 | 91 | name = "12_graphics_pipeline_complete" 92 | path = "src/12_graphics_pipeline_complete.rs" 93 | 94 | [[bin]] 95 | 96 | name = "13_framebuffers" 97 | path = "src/13_framebuffers.rs" 98 | 99 | [[bin]] 100 | 101 | name = "14_command_buffers" 102 | path = "src/14_command_buffers.rs" 103 | 104 | [[bin]] 105 | 106 | name = "15_hello_triangle" 107 | path = "src/15_hello_triangle.rs" 108 | 109 | [[bin]] 110 | 111 | name = "16_swapchain_recreation" 112 | path = "src/16_swapchain_recreation.rs" 113 | 114 | [[bin]] 115 | 116 | name = "17_vertex_input" 117 | path = "src/17_vertex_input.rs" 118 | 119 | [[bin]] 120 | 121 | name = "18_vertex_buffer" 122 | path = "src/18_vertex_buffer.rs" 123 | 124 | [[bin]] 125 | 126 | name = "19_staging_buffer" 127 | path = "src/19_staging_buffer.rs" 128 | 129 | [[bin]] 130 | 131 | name = "20_index_buffer" 132 | path = "src/20_index_buffer.rs" 133 | 134 | [[bin]] 135 | 136 | name = "21_descriptor_set_layout" 137 | path = "src/21_descriptor_set_layout.rs" 138 | 139 | [[bin]] 140 | 141 | name = "22_descriptor_sets" 142 | path = "src/22_descriptor_sets.rs" 143 | 144 | [[bin]] 145 | 146 | name = "23_texture_image" 147 | path = "src/23_texture_image.rs" 148 | 149 | [[bin]] 150 | 151 | name = "24_sampler" 152 | path = "src/24_sampler.rs" 153 | 154 | [[bin]] 155 | 156 | name = "25_texture_mapping" 157 | path = "src/25_texture_mapping.rs" 158 | 159 | [[bin]] 160 | 161 | name = "26_depth_buffering" 162 | path = "src/26_depth_buffering.rs" 163 | 164 | [[bin]] 165 | 166 | name = "27_model_loading" 167 | path = "src/27_model_loading.rs" 168 | 169 | [[bin]] 170 | 171 | name = "28_mipmapping" 172 | path = "src/28_mipmapping.rs" 173 | 174 | [[bin]] 175 | 176 | name = "29_multisampling" 177 | path = "src/29_multisampling.rs" 178 | 179 | [[bin]] 180 | 181 | name = "30_push_constants" 182 | path = "src/30_push_constants.rs" 183 | 184 | [[bin]] 185 | 186 | name = "31_recycling_command_buffers" 187 | path = "src/31_recycling_command_buffers.rs" 188 | 189 | [[bin]] 190 | 191 | name = "32_secondary_command_buffers" 192 | path = "src/32_secondary_command_buffers.rs" 193 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Rust 版 Vulkan 教程的中文翻译,基于 vulkanalia 2 | 3 | 当前进度:**100%!** 4 | 5 | 在线阅读: link:https://vk.7dg.tech[vk.7dg.tech] 6 | 7 | 原文链接: link:https://kylemayes.github.io/vulkanalia/introduction.html[kylemayes.github.io/vulkanalia] 8 | 9 | 特别感谢: 10 | 11 | * link:https://github.com/KyleMayes/vulkanalia[Vulkanalia] 12 | * link:https://github.com/fangcun010/VulkanTutorialCN[Vulkan 中文教程] 13 | * 所有参与本教程翻译的贡献者们 14 | -------------------------------------------------------------------------------- /TERMINOLOGY.csv: -------------------------------------------------------------------------------- 1 | 英文,词性,属性,翻译,注释,例句, 2 | verbose,形容词,一般,冗长,,, 3 | redundant,形容词,一般,冗余/多余,,, 4 | aim at,动词,一般,面向,,, 5 | populate,动词,一般,填充,,, 6 | extension,名词,一般,扩展,用来表示语言扩展、API 扩展等项目,例如 OpenMP 和 VK_KHR_swapchain,, 7 | extension,名词,一般,文件扩展名,,, 8 | invariants,名词,一般,不变式,,, 9 | conformant,形容词,一般,合规的,,, 10 | non-conformant,形容词,一般,不合规的,,, 11 | raw,形容词,一般,原始的,,, 12 | property,名词,一般,属性,,, 13 | trivial,形容词,一般,平凡,,, 14 | non-trivial,形容词,一般,非平凡,,, 15 | attachment,名词,一般,附件,,, 16 | hardcode,动词,一般,硬编码,,, 17 | descriptor,名词,一般,描述符,,, 18 | ,,,,,, 19 | command,名词,程序设计概念,命令,在控制台或者终端中执行的命令,, 20 | buffer,名词,程序设计概念,缓冲,注意我们所有对 Buffer 的翻译都是“缓冲”,没有“区”这个字,这主要是出于一致性的考虑,, 21 | bitmask,名词,程序设计概念,掩码,,, 22 | binding,名词,程序设计概念,绑定,,, 23 | builder,名词,程序设计概念,生成器,《GoF 设计模式》这么翻译的,, 24 | builder pattern,名词,程序设计概念,生成器模式,《GoF 设计模式》这么翻译的,, 25 | instance,名词,程序设计概念,实例,,, 26 | handle,名词,程序设计概念,句柄,,, 27 | pipeline,名词,程序设计概念,管线,Milo Yip 使用“渲染管道”,但从 Google 的结果来看“渲染管线”是更普遍的用法,, 28 | registry,名词,程序设计概念,注册表,不一定是 Windows 注册表,, 29 | namespace,名词,程序设计概念,名称空间,,, 30 | enumeration,名词,程序设计概念,枚举,,, 31 | variant,名词,程序设计概念,枚举变体,,, 32 | signature,名词,程序设计概念,(函数)签名,,, 33 | call,名词,程序设计概念,调用,,, 34 | callback,名词,程序设计概念,回调,,, 35 | component,名词,程序设计概念,组件,,, 36 | console,名词,程序设计概念,控制台,,, 37 | terminal,名词,程序设计概念,终端,,, 38 | executable,名词,程序设计概念,可执行文件,,, 39 | executable,形容词,程序设计概念,可执行,,Make the script executable with `chmod +x compile.sh` and run it, 40 | event loop,名词,程序设计概念,事件循环,,, 41 | wrapper,名词,程序设计概念,封装,,, 42 | flag,名词,程序设计概念,标志,,, 43 | profiling,名词,程序设计概念,性能分析,,, 44 | backwards compatibility,名词,程序设计概念,向后兼容性,,, 45 | field,名词,程序设计概念,字段,,, 46 | method,名词,程序设计概念,方法,,, 47 | constructor,名词,程序设计概念,构造器,,, 48 | initializer,名词,程序设计概念,初始化器,原文中有时候会和 constructor 构造器混用,这种时候一律统一到 constructor,, 49 | platform-specific,形容词,程序设计概念,平台特定的,,, 50 | expose,动词,程序设计概念,暴露,接口/API 等,It exposes a `vk::SurfaceKHR` object that represents an abstract type of surface to present rendered images to, 51 | primitive,名词,程序设计概念,原语,,"The language includes many features to aid in graphics programming, like built-in vector and matrix primitives", 52 | macro,名词,程序设计概念,宏,,, 53 | flush,动词,程序设计概念,冲刷,例如 glFlush 或者 fflush 这种操作,用来同步两个“设备”上的数据,Do keep in mind that this may lead to slightly worse performance than explicit flushing, 54 | fence,名词,程序设计概念,栅栏,用来同步一系列的操作,确保这些操作同时完成,, 55 | semaphore,名词,程序设计概念,信号量,,, 56 | barrier,名词,程序设计概念,屏障,,, 57 | alignment requirement,名词,程序设计概念,对齐要求,翻译成“要求”而不是“需求”,, 58 | blit,名词,程序设计概念,不译,其实就是位块传输操作,, 59 | ,,,,,, 60 | queue,名词,数据结构,队列,,, 61 | ,,,,,, 62 | texture,名词,计算机图形学术语,纹理,,, 63 | texture mapping,名词,计算机图形学术语,纹理映射,,, 64 | texel,名词,计算机图形学术语,纹素,,, 65 | voxel,名词,计算机图形学术语,体素,,, 66 | material,名词,计算机图形学术语,材质,,, 67 | framebuffer,名词,计算机图形学术语,帧缓冲,,, 68 | vertex,名词,计算机图形学术语,顶点,,, 69 | fragment,名词,计算机图形学术语,片元,另一个翻译是片段,但我认为片元更好,, 70 | shader,名词,计算机图形学术语,着色器,,, 71 | shader stage,名词,计算机图形学术语,着色器阶段,C++ 版 Vulkan Tutorial 的翻译是这样的,, 72 | geometry shader,名词,计算机图形学术语,几何着色器,,, 73 | viewport,名词,计算机图形学术语,视口,Milo Yip 使用“视区”,但从 Google 的结果来看“视口”是更普遍的用法,此外“视区”还容易跟一个医学概念混淆,, 74 | render,动词,计算机图形学术语,渲染,,, 75 | surface,名词,计算机图形学术语,表面,,, 76 | surface format,名词,计算机图形学术语,表面格式,,, 77 | graphics card,名词,计算机图形学术语,显卡,,, 78 | mesh,名词,计算机图形学术语,网格,,, 79 | tessellation,名词,计算机图形学术语,曲面细分,也译“镶嵌”或“密铺”,我个人认为曲面细分最符合它在计算机图形学中的用法,, 80 | rasterization,名词,计算机图形学术语,光栅化,,, 81 | rasterizer,名词, 计算机图形学术语,光栅化器,,, 82 | primitive,名词,计算机图形学术语,图元,,, 83 | discretize,动词,计算机图形学术语,离散化,,, 84 | depth test,名词,计算机图形学术语,深度测试,,, 85 | face culling,名词,计算机图形学术语,背面剔除,原文给的 wiki 链接是“背面”剔除;C++ 版的 Vulkan Tutorial 也是这么翻译的,, 86 | mipmap,名词,计算机图形学术语,多级渐远,原文有一半是拉丁文,只能意译这里主要;参考 Milo Yip 的翻译,, 87 | mipmapping levels,名词,计算机图形学术语,多级渐远层级,参考 Milo Yip 对 MIPMAP 的翻译,并参照 Copilot 的建议将“level”译为层级,, 88 | color space,名词,计算机图形学术语,颜色空间,,, 89 | clip coordinates,名词,计算机图形学术语,裁剪坐标,C++ 版 Vulkan Tutorial 的翻译是这样的,, 90 | normalized device coordinate,名词,计算机图形学术语,标准化设备坐标,LearnOpenGL CN 的翻译是这样的,, 91 | homogeneous coordinates,名词,计算机图形学术语,齐次坐标,"Wikipedia, also suggested by copilot,也许应该归类为数学概念",, 92 | fixed functions,名词,计算机图形学术语,固定功能,C++ 版 Vulkan Tutorial 的翻译是这样的,, 93 | instancing,名词,计算机图形学术语,实例化,LearnOpenGL CN 的翻译是这样的,, 94 | input assembly,名词,计算机图形学术语,输入装配(阶段),C++ 版 Vulkan Tutorial 的翻译是这样的,, 95 | input assembler,名词,计算机图形学术语,输入装配器,C++ 版 Vulkan Tutorial 的翻译是这样的,, 96 | multisampling,名词,计算机图形学术语,多重采样,,, 97 | anti-aliasing,名词,计算机图形学术语,抗锯齿,,, 98 | stencil test,名词,计算机图形学术语,模板测试,,, 99 | alpha blending,名词,计算机图形学术语,阿尔法合成,,, 100 | sampler,名词,计算机图形学术语,采样器,,, 101 | uniform,名词,计算机图形学术语,不译,没找到特别理想的翻译,而且大家应该用 uniform 习惯了,, 102 | staging buffer,名词,计算机图形学术语,暂存缓冲,,, 103 | interleaving vertex attributes,名词,计算机图形学术语,交错顶点属性,也就是“紧密打包”的方式,, 104 | instanced rendering,名词,计算机图形学术语,实例化渲染,,, 105 | model,名词,计算机图形学术语,模型,,, 106 | view,名词,计算机图形学术语,视图,,, 107 | projection,名词,计算机图形学术语,投影,,, 108 | up axis,名词,计算机图形学术语,上轴,在 lookat 一类的函数中会用到,指定相机的“上”方向,, 109 | field of view,名词,计算机图形学术语,视野,,, 110 | texel,名词,计算机图形学术语,纹素,即纹理像素 texture pixel,, 111 | voxel,名词,计算机图形学术语,体素,即立体像素或体积像素 volume pixel,, 112 | tiling,名词,计算机图形学术语,平铺,,, 113 | stencil,名词,计算机图形学术语,模板,,, 114 | oversampling,名词,计算机图形学术语,(纹理)采样过密,指的是采样操作相对纹理上的纹素数量过密,, 115 | undersampling,名词,计算机图形学术语,(纹理)采样过疏,,, 116 | anisotropic filtering,名词,计算机图形学术语,各向异性过滤,,, 117 | level of detail,名词,计算机图形学术语,细节层级,,, 118 | lod,名词,计算机图形学术语,细节层级,和 level of detail 一样,, 119 | ,,,,,, 120 | normal,名词,数学概念,法向量/法线,,, 121 | component,名词,数学概念,(向量的)分量,例如一个四维向量有 x y z t 四个分量,, 122 | row major,形容词,数学概念,行主序,,, 123 | column major,形容词,数学概念,列主序,,, 124 | ,,,,,, 125 | queue family,名词,Vulkan概念,队列族,C++ 版 Vulkan Tutorial 的翻译是这样的,, 126 | queue families,名词,Vulkan概念,队列族,,, 127 | swapchain,名词,Vulkan概念,交换链,,, 128 | presentation,名词,Vulkan概念,呈现,,, 129 | present mode,名词,Vulkan概念,呈现模式,,, 130 | image view,名词,Vulkan概念,图像视图,,, 131 | render pass,名词,Vulkan概念,渲染流程,,, 132 | command pool,名词,Vulkan概念,指令池,,, 133 | command buffer,名词,Vulkan概念,指令缓冲,,, 134 | primary command buffer,名词,Vulkan概念,主指令缓冲,,, 135 | secondary command buffer,名词,Vulkan概念,次级指令缓冲,,, 136 | validation layer,名词,Vulkan概念,校验层,C++ 版 Vulkan Tutorial 的翻译是这样的,, 137 | image view,名词,Vulkan概念,图像视图,,, 138 | swap extent,名词,Vulkan概念,交换范围,,, 139 | push constant,名词,Vulkan概念,推送常量,一种 Vulkan 技术,可以将动态数据高效地“推送”到着色器,, 140 | command,名词,Vulkan概念,指令,经由 Vulkan API 提交到 GPU 上执行的指令,例如绘制指令、计算指令、内存拷贝指令等,, 141 | command,名词,Vulkan概念,函数,"Vulkan API 中的函数,例如 vkCreateGraphicsPipeline。尽管 Vulkan specification 使用了这个术语,但是我觉得没必要;而且很容易跟上面那个 command 搞混。C++ 版的教程也是这么干的。",, 142 | uniform buffer object,名词,Vulkan概念,uniform 缓冲对象,注意我们不翻译 uniform 这个词,, 143 | descriptor set,名词,Vulkan概念,描述符集合,,, 144 | descriptor set layout,名词,Vulkan概念,描述符集合布局,,, 145 | combined image sampler,名词,Vulkan概念,组合图像采样器,,, 146 | addressing mode,名词,Vulkan概念,寻址模式,,, 147 | ,,,,,, 148 | C style,形容词,C语言概念,类 C,,GLSL is a shading language with a C-style syntax, 149 | null-terminated C string,名词,C语言概念,以空字符结尾的C字符串,,, 150 | ,,,,,, 151 | associated constant,名词,Rust概念,关联常量,,, 152 | lifetime,名词,Rust概念,生存期,我认为“生命周期”属于误译,显然生命周期应该指 Vue/React 的 lifecycle,而 Rust 的 lifetime 里没有“周”,, 153 | prelude,名词,Rust概念,不译,,, 154 | crate,名词,Rust概念,不译,,, 155 | trait,名词,Rust概念,不译,,, 156 | ,,,,,, 157 | issue,名词,Git概念,问题,之后可能会回退,, 158 | pull request,名词,Git概念,拉取请求,之后可能会回退,, 159 | ,,,,,, 160 | subresource,名词,未知,子资源,需要考证,, 161 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | 3 | title = "Vulkan 教程(Rust)" 4 | authors = ["Kyle Mayes", "Chuigda WhiteGive (翻译者)"] 5 | 6 | description = "Vulkan 教程(Rust)的中文翻译" 7 | 8 | language = "zh" 9 | multilingual = false 10 | 11 | src = "src" 12 | 13 | edition = "edition2018" 14 | 15 | [output.html] 16 | 17 | git-repository-url = "https://github.com/chuigda/Vulkan-Tutorial-Rust-CN" 18 | 19 | [preprocessor.vk] 20 | 21 | command = "vk-preprocessor" 22 | renderer = ["html"] 23 | -------------------------------------------------------------------------------- /book/preprocessor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | publish = false 4 | 5 | edition = "2018" 6 | 7 | name = "preprocessor" 8 | authors = ["Kyle Mayes "] 9 | 10 | version = "0.1.0" 11 | 12 | readme = "../README.md" 13 | license = "Apache-2.0" 14 | 15 | description = "Preprocessor." 16 | 17 | build = "build.rs" 18 | 19 | [dependencies] 20 | 21 | anyhow = "1" 22 | clap = "2" 23 | log = "0.4" 24 | mdbook = { version = "=0.4.21", default-features = false } 25 | pretty_env_logger = "0.5" 26 | pulldown-cmark = "0.8" 27 | pulldown-cmark-to-cmark = "6" 28 | serde_json = "1" 29 | 30 | [[bin]] 31 | 32 | name = "vk-preprocessor" 33 | path = "main.rs" 34 | -------------------------------------------------------------------------------- /book/preprocessor/build.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | use std::fs::File; 4 | use std::io::Write; 5 | use std::path::Path; 6 | 7 | fn main() { 8 | // Write an empty `index.txt` file if it doesn't exist so we can compile the 9 | // preprocessor without needing to generate `index.txt`. 10 | let index = Path::new("../../../index.txt"); 11 | if !index.exists() { 12 | let mut index = File::create(index).unwrap(); 13 | index.write_all(b"").unwrap(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /book/preprocessor/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | use std::collections::HashMap; 4 | use std::io; 5 | use std::process; 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | 8 | use anyhow::{anyhow, Result}; 9 | use clap::{App, Arg, SubCommand}; 10 | use log::*; 11 | use mdbook::book::BookItem; 12 | use mdbook::preprocess::CmdPreprocessor; 13 | use pulldown_cmark::{Event, Parser, Tag}; 14 | use pulldown_cmark_to_cmark::cmark; 15 | 16 | /// The Vulkan API Registry index. 17 | const INDEX: &str = include_str!("../../../index.txt"); 18 | /// The version of `vulkanalia` used by the tutorial. 19 | const VERSION: &str = "0.25.0"; 20 | 21 | /// The number of documentation link replacements made. 22 | static REPLACEMENTS: AtomicUsize = AtomicUsize::new(0); 23 | 24 | #[rustfmt::skip] 25 | pub fn app() -> App<'static, 'static> { 26 | App::new("vk-preprocessor") 27 | .about("An mdbook preprocessor for the tutorial book.") 28 | .subcommand(SubCommand::with_name("supports") 29 | .arg(Arg::with_name("renderer").required(true)) 30 | .about("Check whether a renderer is supported by this preprocessor")) 31 | } 32 | 33 | fn main() -> Result<()> { 34 | pretty_env_logger::init(); 35 | 36 | // Check renderer support. 37 | if let Some(args) = app().get_matches().subcommand_matches("supports") { 38 | let renderer = args.value_of("renderer").unwrap(); 39 | process::exit(if renderer == "html" { 0 } else { 1 }); 40 | } 41 | 42 | // Parse the book and check version compatibility. 43 | let (context, mut book) = CmdPreprocessor::parse_input(io::stdin())?; 44 | if context.mdbook_version != mdbook::MDBOOK_VERSION { 45 | return Err(anyhow!( 46 | "Preprocessor build with mdbook {}, called with mdbook {}.", 47 | mdbook::MDBOOK_VERSION, 48 | context.mdbook_version 49 | )); 50 | } 51 | 52 | // Preprocess the book. 53 | let index = load_index(); 54 | book.for_each_mut(|i| preprocess_item(i, &index).unwrap()); 55 | serde_json::to_writer(io::stdout(), &book)?; 56 | 57 | let replacements = REPLACEMENTS.load(Ordering::Relaxed); 58 | info!("Made {} documentation link replacements.", replacements); 59 | 60 | Ok(()) 61 | } 62 | 63 | #[rustfmt::skip] 64 | fn load_index() -> HashMap<&'static str, &'static str> { 65 | let mut index = INDEX 66 | .lines() 67 | .map(|l| { 68 | let mut tokens = l.split('\t'); 69 | let name = tokens.next().unwrap(); 70 | let path = tokens.next().unwrap(); 71 | (name, path) 72 | }) 73 | .collect::>(); 74 | 75 | info!("Loaded index has {} entries.", index.len()); 76 | 77 | // Add entries for non-generated items. 78 | index.insert("Bytecode", "https://docs.rs/vulkanalia/%VERSION%/vulkanalia/bytecode/struct.Bytecode.html"); 79 | index.insert("Device", "https://docs.rs/vulkanalia/%VERSION%/vulkanalia/struct.Device.html"); 80 | index.insert("Entry", "https://docs.rs/vulkanalia/%VERSION%/vulkanalia/struct.Entry.html"); 81 | index.insert("Instance", "https://docs.rs/vulkanalia/%VERSION%/vulkanalia/struct.Instance.html"); 82 | index.insert("vk_window::create_surface", "https://docs.rs/vulkanalia/%VERSION%/vulkanalia/window/fn.create_surface.html"); 83 | index.insert("vk_window::get_required_instance_extensions", "https://docs.rs/vulkanalia/%VERSION%/vulkanalia/window/fn.get_required_instance_extensions.html"); 84 | 85 | index 86 | } 87 | 88 | fn preprocess_item(item: &mut BookItem, index: &HashMap<&str, &str>) -> Result<()> { 89 | if let BookItem::Chapter(ref mut chapter) = item { 90 | let parser = Parser::new(&chapter.content); 91 | let events = parser.into_iter().map(|e| map_event(e, index)); 92 | let mut buffer = String::with_capacity(chapter.content.len() * 2); 93 | cmark(events, &mut buffer, None)?; 94 | chapter.content = buffer; 95 | } 96 | 97 | Ok(()) 98 | } 99 | 100 | fn map_event<'e>(event: Event<'e>, index: &HashMap<&str, &str>) -> Event<'e> { 101 | if let Event::Code(code) = &event { 102 | if let Some(url) = index.get(&code[..]) { 103 | REPLACEMENTS.fetch_add(1, Ordering::Relaxed); 104 | let url = url.replace("%VERSION%", VERSION); 105 | Event::Html(format!("{}", url, code).into()) 106 | } else if let Some(code) = code.strip_prefix('^') { 107 | Event::Code(code.to_string().into()) 108 | } else { 109 | event 110 | } 111 | } else if let Event::End(Tag::Link(ltype, url, title)) = &event { 112 | let url = url.replace("%VERSION%", VERSION).into(); 113 | Event::End(Tag::Link(*ltype, url, title.clone())) 114 | } else { 115 | event 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # 总览 2 | 3 | - [介绍](./introduction.md) 4 | - [概览](./overview.md) 5 | - [开发环境](./development_environment.md) 6 | - [FAQ](./faq.md) 7 | 8 | # 基本设置 9 | 10 | - [基础代码](./setup/base_code.md) 11 | - [Vulkan 实例](./setup/instance.md) 12 | - [校验层](./setup/validation_layers.md) 13 | - [物理设备与队列族](./setup/physical_devices_and_queue_families.md) 14 | - [逻辑设备与队列](./setup/logical_device_and_queues.md) 15 | 16 | # 呈现 17 | 18 | - [窗口表面](./presentation/window_surface.md) 19 | - [交换链](./presentation/swapchain.md) 20 | - [图像视图](./presentation/image_views.md) 21 | 22 | # 管线 23 | 24 | - [介绍](./pipeline/introduction.md) 25 | - [着色器模块](./pipeline/shader_modules.md) 26 | - [固定功能](./pipeline/fixed_functions.md) 27 | - [渲染流程](./pipeline/render_passes.md) 28 | - [总结](./pipeline/conclusion.md) 29 | 30 | # 绘制 31 | 32 | - [帧缓冲](./drawing/framebuffers.md) 33 | - [指令缓冲](./drawing/command_buffers.md) 34 | - [渲染与呈现](./drawing/rendering_and_presentation.md) 35 | 36 | # 交换链 37 | 38 | - [重建交换链](./swapchain/recreation.md) 39 | 40 | # 顶点缓冲 41 | 42 | - [描述顶点输入](./vertex/vertex_input_description.md) 43 | - [创建顶点缓冲](./vertex/vertex_buffer_creation.md) 44 | - [暂存缓冲](./vertex/staging_buffer.md) 45 | - [索引缓冲](./vertex/index_buffer.md) 46 | 47 | # Uniform 缓冲 48 | 49 | - [描述符集合布局与缓冲](./uniform/descriptor_set_layout_and_buffer.md) 50 | - [描述符池与描述符集合](./uniform/descriptor_pool_and_sets.md) 51 | 52 | # 纹理映射 53 | 54 | - [图像](./texture/images.md) 55 | - [图像视图与采样器](./texture/image_view_and_sampler.md) 56 | - [组合图像采样器](./texture/combined_image_sampler.md) 57 | 58 | # 模型 59 | 60 | - [深度缓冲](./model/depth_buffering.md) 61 | - [加载模型](./model/loading_models.md) 62 | 63 | # 渲染质量 64 | 65 | - [生成多级渐远](./quality/generating_mipmaps.md) 66 | - [多重采样](./quality/multisampling.md) 67 | 68 | # 动态场景 69 | 70 | - [推送常量](./dynamic/push_constants.md) 71 | - [重用指令缓冲](./dynamic/recycling_command_buffers.md) 72 | - [次级指令缓冲](./dynamic/secondary_command_buffers.md) 73 | 74 | # 总结 75 | 76 | - [总结](./conclusion.md) 77 | -------------------------------------------------------------------------------- /book/src/conclusion.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | 一路千辛万苦,我们终于有了一个基本的 Vulkan 程序。你现在已经掌握了 Vulkan 的基本原理,可以开始探索更多的特性了,比如: 8 | * 实例化渲染 9 | * 动态 uniform 10 | * 分离图像和采样器描述符 11 | * 管线缓存 12 | * 多线程指令缓冲生成 13 | * 多个子通道 14 | * 计算着色器 15 | 16 | 现在的程序能被以多种形式扩展,比如添加 Blinn-Phong 光照、后处理效果和阴影映射。你应该能够从其他 API 的教程中学习到这些效果的工作原理,因为尽管 Vulkan 是显式的,但是很多概念仍然是相同的。 17 | -------------------------------------------------------------------------------- /book/src/development_environment.md: -------------------------------------------------------------------------------- 1 | # 开发环境 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | 在这个章节中,我们将会安装 Vulkan SDK 并搭建开发 Vulkan 应用所需的环境。此教程假设你已经有一个搭建好的 Rust(1.70+)开发环境。 8 | 9 | ## Cargo 项目 10 | 11 | 首先,我们创建一个 Cargo 项目: 12 | 13 | `cargo new vulkan-tutorial` 14 | 15 | 在执行这个命令后,你会看到一个叫做 `vulkan-tutorial` 的文件夹,里面有一个简单的生成 Rust 可执行文件的 Cargo 项目。 16 | 17 | 打开这个文件夹里的 `Cargo.toml` 文件,并且将下列依赖加入其中的 `[dependencies]` 部分: 18 | 19 | ```toml 20 | anyhow = "1" 21 | log = "0.4" 22 | cgmath = "0.18" 23 | nalgebra-glm = "0.18" 24 | png = "0.17" 25 | pretty_env_logger = "0.5" 26 | thiserror = "1" 27 | tobj = { version = "3", features = ["log"] } 28 | vulkanalia = { version = "=0.25.0", features = ["libloading", "provisional", "window"] } 29 | winit = "0.29" 30 | ``` 31 | 32 | * `anyhow` – 用于简单的错误处理 33 | * `log` – 日志库 34 | * `cgmath` – 一个 Rust 语言的 [GLM](https://glm.g-truc.net/0.9.9/index.html)(graphics math library,图形数学库)替代 35 | * `png` – 用于将 PNG 图片文件加载到纹理 36 | * `pretty_env_logger` – 用于打印日志到控制台 37 | * `thiserror` – 用于在自定义错误类型时减少样板代码 38 | * `tobj` – 用于加载 [Wavefront .obj 格式](https://en.wikipedia.org/wiki/Wavefront_.obj_file) 的 3D 模型 39 | * `vulkanalia` – 用于调用 Vulkan API 40 | * `winit` – 用于创建将进行渲染的窗口 41 | 42 | ## Vulkan SDK 43 | 44 | 在开发 Vulkan 应用时需要用到的最关键的组件就是 Vulkan SDK。它包含了头文件、标准校验层、调试工具,以及一个 Vulkan 函数加载器。加载器将会在运行时从驱动中寻找 Vulkan 函数,如果你熟悉 OpenGL 的话,它的功能与 GLEW 类似。 45 | 46 | ### Windows 47 | 48 | SDK 能在 [LunarG 网站](https://vulkan.lunarg.com/)下载。创建账户不是必须的,但它会给你阅读一些额外文档的权限,这些文档或许对你有用。 49 | 50 | ![](./images/vulkan_sdk_download_buttons.png) 51 | 52 | 继续完成安装,并且注意 SDK 的安装路径。我们需要做的第一件事就是验证你的显卡与驱动支持 Vulkan。进入 SDK 的安装路径,打开 `Bin` 文件夹并且运行 `vkcube.exe` 示例应用。你应该会看到这个画面: 53 | 54 | ![](./images/cube_demo.png) 55 | 56 | 如果你收到了一条错误信息,那你需要确保你的显卡驱动是最新的,包含 Vulkan 运行时,并且你的显卡支持 Vulkan。主流品牌的驱动下载链接详见[介绍章节](introduction.html)。 57 | 58 | 这个文件夹里有另外两个对开发很有用的程序。`glslangValidator.exe` 和 `glslc.exe` 将会把人类可阅读的 [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) (OpenGL Shading Language,OpenGL 着色器语言)代码编译为字节码。我们将会在[着色器模块章节](pipeline/shader_modules.html)深入讨论这部分内容。`Bin` 文件夹也包含了 Vulkan 加载器与校验层的二进制文件;`Lib` 文件夹则包含了库。 59 | 60 | 你可以自由地探索其它文件,但本教程并不会用到它们。 61 | 62 | ### Linux 63 | 64 | 以下操作说明面向 Ubuntu 用户,非 Ubuntu 用户也可以将 `apt` 命令换成合适的你使用的包管理器的命令。 65 | 66 | 在 Linux 上开发 Vulkan 应用时需要用到的最关键的组件是 Vulkan 加载器,校验层,以及一些用来测试你的机器是否支持 Vulkan 的命令行实用工具: 67 | 68 | * `sudo apt install vulkan-tools` – 命令行实用工具,最关键的两个是 `vulkaninfo` 和 `vkcube`。运行这两个命令来测试你的机器是否支持 Vulkan。 69 | * `sudo apt install libvulkan-dev` – 安装 Vulkan 加载器。加载器将会在运行时从驱动中寻找这些函数,如果你熟悉 OpenGL 的话,它的功能与 GLEW 类似。 70 | * `sudo apt install vulkan-validationlayers-dev` – 安装标准校验层。这在调试 Vulkan 应用程序时非常关键,我们会在之后的章节中讨论这部分内容。 71 | 72 | 如果你安装成功了,你在 Vulkan 部分没有别的需要做的了。记得运行 `vkcube` 并确保你可以在一个窗口中看见这个画面: 73 | 74 | ![](./images/cube_demo_nowindow.png) 75 | 76 | 如果你收到了一条错误信息,那你需要确保你的显卡驱动是最新的,包含 Vulkan 运行时,并且你的显卡支持 Vulkan。主流品牌的驱动下载链接详见[介绍章节](introduction.html)。 77 | 78 | ### MacOS 79 | 80 | SDK 可在 [LunarG 网站](https://vulkan.lunarg.com/) 下载。创建账户不是必须的,但它会给你阅读一些或许对你有用的额外文档的权限。 81 | 82 | ![](./images/vulkan_sdk_download_buttons.png) 83 | 84 | MacOS 版本的 SDK 在内部使用了 [MoltenVK](https://moltengl.com/)。Vulkan 在 MacOS 上没有原生支持,所以 MoltenVK 会作为中间层把 Vulkan API 的调用翻译至苹果的 Metal 图形框架。这样你就可以享受到苹果的 Metal 框架在调试与性能上的优点。 85 | 86 | 下载完成之后,将其解压到你自己选择的文件夹。在解压后的文件夹内,你可以在 `Applications` 文件夹中找到一些使用 SDK 运行的示例应用的可执行文件。运行 `vkcube` 示例应用,你会看到这个画面: 87 | 88 | ![](./images/cube_demo_mac.png) 89 | 90 | #### 环境配置 91 | 92 | 当在 Vulkan SDK 目录以外的地方运行 Vulkan 应用程序时,你可能会需要运行由 Vulkan SDK 提供的 `setup-env.sh` 脚本,以避免找不到 Vulkan 库(例如 `libvulkan.dylib`)导致的问题。如果你把 Vulkan SDK 安装到了默认位置,脚本应该可以在如下目录中找到:`~/VulkanSDK/1.3.280.1/setup-env.sh`(用你实际安装的版本号替换 `1.3.280.1` 以匹配你的 Vulkan 安装)。 93 | 94 | 你也可以把这个脚本添加到你 Shell 的启动脚本中,这样它就能自动运行了。例如你可以在 `~/.zshrc` 文件中添加这样一行: 95 | 96 | ``` 97 | source ~/VulkanSDK/1.3.280.1/setup-env.sh 98 | ``` 99 | -------------------------------------------------------------------------------- /book/src/drawing/command_buffers.md: -------------------------------------------------------------------------------- 1 | # 指令缓冲(Command buffers) 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/14_command_buffers.rs) 8 | 9 | Vulkan 中的指令 —— 例如绘制操作和内存传输操作 —— 并不是通过直接调用函数来执行的。你需要把你想执行的操作记录在指令缓冲对象中。这样做的优势在于绘制指令可以提前配置好,并且可以在多个线程中配置指令。在配置完指令缓冲之后,你只要在主循环中告诉 Vulkan 执行这些指令就可以了。 10 | 11 | ## 指令池(Command pools) 12 | 13 | 在创建指令缓冲之前,我们需要先创建一个指令池。指令池管理着用于存储指令缓冲的内存,我们将从指令池中分配指令缓冲。在 `AppData` 中添加一个新的字段 `vk::CommandPool` 来存储指令池: 14 | 15 | ```rust,noplaypen 16 | struct AppData { 17 | // ... 18 | command_pool: vk::CommandPool, 19 | } 20 | ``` 21 | 22 | 接着创建一个新的函数 `create_command_pool` 并在 `App::create` 中创建完帧缓冲后调用它: 23 | 24 | ```rust,noplaypen 25 | impl App { 26 | unsafe fn create(window: &Window) -> Result { 27 | // ... 28 | create_framebuffers(&device, &mut data)?; 29 | create_command_pool(&instance, &device, &mut data)?; 30 | // ... 31 | } 32 | } 33 | 34 | unsafe fn create_command_pool( 35 | instance: &Instance, 36 | device: &Device, 37 | data: &mut AppData, 38 | ) -> Result<()> { 39 | Ok(()) 40 | } 41 | ``` 42 | 43 | 创建指令池只需要两个参数: 44 | 45 | ```rust,noplaypen 46 | let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?; 47 | 48 | let info = vk::CommandPoolCreateInfo::builder() 49 | .flags(vk::CommandPoolCreateFlags::empty()) // 可选 50 | .queue_family_index(indices.graphics); 51 | ``` 52 | 53 | 指令缓冲是通过提交到一个设备队列 —— 例如图形队列或呈现队列 —— 来执行的。每个指令池分配的指令缓冲只能提交到一种队列。这里我们要记录用于绘制的指令,所以我们选择图形队列族。 54 | 55 | 指令池可以有三个标志: 56 | 57 | * `vk::CommandPoolCreateFlags::TRANSIENT` – 提示指令缓冲会经常被重新记录(可能会改变内存分配行为) 58 | * `vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER` – 允许单独重新记录指令缓冲,如果没有这个标志,所有指令缓冲都必须一起重置 59 | * `vk::CommandPoolCreateFlags::PROTECTED` – 创建“受保护”的指令缓冲,它们存储在[“受保护”内存](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#memory-protected-access-rules)中,Vulkan 会阻止对该内存未授权的访问 60 | 61 | 我们只在程序开始的时候记录指令缓冲,然后在主循环中重复执行它们,并且我们也不需要使用 DRM 来保护我们的三角形,所以我们不使用任何标志。 62 | 63 | ```rust,noplaypen 64 | data.command_pool = device.create_command_pool(&info, None)?; 65 | ``` 66 | 67 | 从指令池分配的指令缓冲会在整个程序中被使用,所以缓冲池应该在程序结束时销毁: 68 | 69 | ```rust,noplaypen 70 | unsafe fn destroy(&mut self) { 71 | self.device.destroy_command_pool(self.data.command_pool, None); 72 | // ... 73 | } 74 | ``` 75 | 76 | ## 分配指令缓冲 77 | 78 | 现在我们可以开始分配指令缓冲,并在其中记录绘制指令了。因为某个绘制指令涉及到绑定正确的 `vk::Framebuffer`,所以我们实际上要为交换链中的每张图像都记录一个指令缓冲。为此,我们在 `AppData` 中创建一个 `vk::CommandBuffer` 对象的列表。指令缓冲会在它们所属的指令池被销毁时自动释放,所以我们不需要进行显式的清理。 79 | 80 | ```rust,noplaypen 81 | struct AppData { 82 | // ... 83 | command_buffers: Vec, 84 | } 85 | ``` 86 | 87 | 接下来我们开始实现用于分配并记录指令缓冲的 `create_command_buffers` 函数。 88 | 89 | ```rust,noplaypen 90 | impl App { 91 | unsafe fn create(window: &Window) -> Result { 92 | // ... 93 | create_command_pool(&instance, &device, &mut data)?; 94 | create_command_buffers(&device, &mut data)?; 95 | // ... 96 | } 97 | } 98 | 99 | unsafe fn create_command_buffers(device: &Device, data: &mut AppData) -> Result<()> { 100 | Ok(()) 101 | } 102 | ``` 103 | 104 | 指令缓冲由 `allocate_command_buffers` 函数分配,它接受一个 `vk::CommandBufferAllocateInfo` 结构体作为参数,这个结构体指定了指令池和要分配的指令缓冲的数量: 105 | 106 | ```rust,noplaypen 107 | let allocate_info = vk::CommandBufferAllocateInfo::builder() 108 | .command_pool(data.command_pool) 109 | .level(vk::CommandBufferLevel::PRIMARY) 110 | .command_buffer_count(data.framebuffers.len() as u32); 111 | 112 | data.command_buffers = device.allocate_command_buffers(&allocate_info)?; 113 | ``` 114 | 115 | `level` 参数指定了分配的指令缓冲是主指令缓冲还是次级指令缓冲。 116 | 117 | * `vk::CommandBufferLevel::PRIMARY` – 可以提交到队列执行,但不能从其他指令缓冲中调用 118 | * `vk::CommandBufferLevel::SECONDARY` – 不能直接提交,但可以从主指令缓冲中调用 119 | 120 | 这里我们用不到次级指令缓冲,不过不过你能想到,次级指令缓冲对于复用主指令缓冲中的常用操作很有帮助。 121 | 122 | ## 开始记录指令缓冲 123 | 124 | 我们调用 `begin_command_buffer` 函数来开始记录指令缓冲,它接受一个 `vk::CommandBufferBeginInfo` 结构体作为参数,这个结构体指定一些有关指令缓冲使用方式的细节。 125 | 126 | ```rust,noplaypen 127 | for (i, command_buffer) in data.command_buffers.iter().enumerate() { 128 | let inheritance = vk::CommandBufferInheritanceInfo::builder(); 129 | 130 | let info = vk::CommandBufferBeginInfo::builder() 131 | .flags(vk::CommandBufferUsageFlags::empty()) // 可选 132 | .inheritance_info(&inheritance); // 可选 133 | 134 | device.begin_command_buffer(*command_buffer, &info)?; 135 | } 136 | ``` 137 | 138 | `flag` 参数指定了我们将要如何使用这个指令缓冲,它可以有以下取值: 139 | 140 | * `vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT` – 指令缓冲会在执行一次之后重新记录 141 | * `vk::CommandBufferUsageFlags::RENDER_PASS_CONTINUE` – 这是一个次级指令缓冲,它会完全在一个渲染流程中执行 142 | * `vk::CommandBufferUsageFlags::SIMULTANEOUS_USE` – 指令缓冲可以在它还在等待执行的时候被重新提交 143 | 144 | 目前我们还不需要这些标志。 145 | 146 | `inheritance_info` 参数只用于次级指令缓冲,它指定了要从调用它的主指令缓冲中继承哪些状态。 147 | 148 | 如果指令缓冲已经被记录过一次,调用 `begin_command_buffer` 会隐式地重置它。一旦记录完成,就不能再向指令缓冲中追加指令了。 149 | 150 | ## 开始渲染流程 151 | 152 | 在我们开始渲染流程之前,我们需要先构建一些参数。 153 | 154 | ```rust,noplaypen 155 | let render_area = vk::Rect2D::builder() 156 | .offset(vk::Offset2D::default()) 157 | .extent(data.swapchain_extent); 158 | ``` 159 | 160 | 这里我们定义了渲染区域的大小。渲染区域定义了在渲染流程执行期间着色器会在哪里加载和存储像素。渲染区域之外的像素的值是未定义的。渲染区域应该和附件的大小匹配以获得最佳性能。 161 | 162 | ```rust,noplaypen 163 | let color_clear_value = vk::ClearValue { 164 | color: vk::ClearColorValue { 165 | float32: [0.0, 0.0, 0.0, 1.0], 166 | }, 167 | }; 168 | ``` 169 | 170 | 接着我们定义一个清除值,它会被用来在渲染流程开始时清空帧缓冲(因为我们在创建渲染流程的时候指定了 `vk::AttachmentLoadOp::CLEAR`)。`vk::ClearValue` 是一个联合体(union),它可以用来设置颜色附件的清除值,也可以用来设置深度/模板附件的清除值。这里我们设置了 `vk::ClearColorValue` 类型的 `color` 字段,用来将清除颜色设为不透明的黑色。 171 | 172 | 绘制以 `cmd_begin_render_pass` 启动渲染流程开始,渲染流程由 `vk::RenderPassBeginInfo` 结构体来配置: 173 | 174 | ```rust,noplaypen 175 | let clear_values = &[color_clear_value]; 176 | let info = vk::RenderPassBeginInfo::builder() 177 | .render_pass(data.render_pass) 178 | .framebuffer(data.framebuffers[i]) 179 | .render_area(render_area) 180 | .clear_values(clear_values); 181 | ``` 182 | 183 | 首先我们提供渲染流程和将要绑定的附件。之前,我们为交换链中的每个图像都创建了一个帧缓冲,用作颜色附件。然后我们提供刚才创建的渲染区域和清除值。 184 | 185 | ```rust,noplaypen 186 | device.cmd_begin_render_pass( 187 | *command_buffer, &info, vk::SubpassContents::INLINE); 188 | ``` 189 | 190 | 现在渲染流程可以开始了。所有记录指令的函数都以 `cmd_` 前缀开头。它们都返回 `()`,所以所以我们在完成记录之前都不需要进行错误处理。 191 | 192 | 每个记录指令的函数的第一个参数都是用来记录指令的指令缓冲。第二个参数指定刚才提供的的渲染流程的细节。最后一个参数控制渲染流程中的绘制指令是如何提供的。它可以有以下两个值: 193 | 194 | * `vk::SubpassContents::INLINE` – 渲染流程中的指令会被嵌入到主指令缓冲中,不会执行任何次级指令缓冲 195 | * `vk::SubpassContents::SECONDARY_COMMAND_BUFFERS` – 渲染流程中的指令会被从次级指令缓冲中执行 196 | 197 | 我们不会使用次级指令缓冲,所以我们选择第一个选项。 198 | 199 | ## 基本绘制指令 200 | 201 | 现在我们可以绑定图形管线: 202 | 203 | ```rust,noplaypen 204 | device.cmd_bind_pipeline( 205 | *command_buffer, vk::PipelineBindPoint::GRAPHICS, data.pipeline); 206 | ``` 207 | 208 | 第二个参数指定了管线对象是图形管线还是计算管线。至此,我们已经告诉 Vulkan 在图形管线中执行哪些操作,以及在片元着色器中使用哪个附件,剩下的就是告诉它绘制三角形: 209 | 210 | ```rust,noplaypen 211 | device.cmd_draw(*command_buffer, 3, 1, 0, 0); 212 | ``` 213 | 214 | 这个实际的绘制函数有点虎头蛇尾。我们之前提供了那么多信息,实际的绘制函数却如此简单。除了指令缓冲之外,它还有以下参数: 215 | 216 | * `vertex_count` – 尽管我们没有顶点缓冲,技术上来说,我们是要绘制 3 个顶点。 217 | * `instance_count` – 用于实例化渲染,如果你没在进行实例化渲染,就把它设为 `1`。 218 | * `first_vertex` – 顶点缓冲的偏移量,定义了 `gl_VertexIndex` 的最小值。 219 | * `first_instance` – 实例化渲染的偏移量,定义了 `gl_InstanceIndex` 的最小值。 220 | 221 | ## 完成 222 | 223 | 最后,我们调用 `cmd_end_render_pass` 函数结束渲染流程: 224 | 225 | ```rust,noplaypen 226 | device.cmd_end_render_pass(*command_buffer); 227 | ``` 228 | 229 | 并调用 `end_command_buffer` 结束记录指令缓冲: 230 | 231 | ```rust,noplaypen 232 | device.end_command_buffer(*command_buffer)?; 233 | ``` 234 | 235 | 在下一章中,我们将编写主循环的代码,它将从交换链中获取图像,执行正确的指令缓冲,并将完成的图像返回给交换链。 236 | -------------------------------------------------------------------------------- /book/src/drawing/framebuffers.md: -------------------------------------------------------------------------------- 1 | # 帧缓冲 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/13_framebuffers.rs) 8 | 9 | 我们在之前的几个章节中多次提到过帧缓冲。而现在我们已经建立了渲染流程,就差一个跟交换链图片格式相同的帧缓冲了。 10 | 11 | 12 | 在创建渲染流程时指定的附件需要被包装进帧缓冲对象 `vk::Framebuffer` 中进行绑定。一个帧缓冲对象引用了所有代表附件的 `vk::ImageView` 对象。在我们的例子中,我们只有一个颜色附件。但这并不意味着我们就只需要使用一张图像,因为我们需要为交换链中的每个图像都创建对应的帧缓冲,并在渲染时使用与从交换链取得的图像对应的帧缓冲。 13 | 14 | 为此,我们在 `AppData` 中创建另一个 `Vec` 字段来存放帧缓冲: 15 | 16 | ```rust,noplaypen 17 | struct AppData { 18 | // ... 19 | framebuffers: Vec, 20 | } 21 | ``` 22 | 23 | 我们创建一个新的函数 `create_framebuffers` ,在 `App::create` 里创建完管线之后调用它。之后我们将会在这个函数里创建 `vk::Framebuffer` 对象并将它们存储在 `framebuffers` 数组中: 24 | 25 | ```rust,noplaypen 26 | impl App { 27 | unsafe fn create(window: &Window) -> Result { 28 | // ... 29 | create_pipeline(&device, &mut data)?; 30 | create_framebuffers(&device, &mut data)?; 31 | // ... 32 | } 33 | } 34 | 35 | unsafe fn create_framebuffers(device: &Device, data: &mut AppData) -> Result<()> { 36 | Ok(()) 37 | } 38 | ``` 39 | 40 | `create_framebuffers` 函数将遍历交换链中所有图像视图,为每个图像视图创建一个帧缓冲: 41 | 42 | ```rust,noplaypen 43 | unsafe fn create_framebuffers(device: &Device, data: &mut AppData) -> Result<()> { 44 | data.framebuffers = data 45 | .swapchain_image_views 46 | .iter() 47 | .map(|i| { 48 | let attachments = &[*i]; 49 | let create_info = vk::FramebufferCreateInfo::builder() 50 | .render_pass(data.render_pass) 51 | .attachments(attachments) 52 | .width(data.swapchain_extent.width) 53 | .height(data.swapchain_extent.height) 54 | .layers(1); 55 | 56 | device.create_framebuffer(&create_info, None) 57 | }) 58 | .collect::, _>>()?; 59 | 60 | Ok(()) 61 | } 62 | ``` 63 | 64 | 如你所见,创建帧缓冲的方式相当直白。首先我们需要指定帧缓冲要与哪个 `render_pass` 兼容。你只能在与一个帧缓冲兼容的渲染流程上使用它,这基本上就意味着渲染流程和帧缓冲有着相同数量和类型的附件。 65 | 66 | `attachments` 字段指定了应绑定到渲染流程的 `attachment` 数组所对应的附件描述的 `vk::ImageView` 对象。 67 | 68 | `width` 和 `height` 没什么好说的,就是宽度和高度,`layers` 则指的是图像的*层*数。我们交换链中的图像都是单个图像,所以层数为`1`。 69 | 70 | 我们应该在帧缓冲所基于的图像视图和渲染流程被销毁之前清理它: 71 | 72 | ```rust,noplaypen 73 | unsafe fn destroy(&mut self) { 74 | self.data.framebuffers 75 | .iter() 76 | .for_each(|f| self.device.destroy_framebuffer(*f, None)); 77 | // ... 78 | } 79 | ``` 80 | 81 | 现在我们到达了一个里程碑,我们拥有了渲染所需的所有对象。在下一章中,我们将编写第一批实际的绘制指令。 82 | -------------------------------------------------------------------------------- /book/src/dynamic/push_constants.md: -------------------------------------------------------------------------------- 1 | # 推送常量 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | > 前面没有这个声明的章节都是直接从 改编而来。

这一章和后面的章节都是原创,作者并不是 Vulkan 的专家。作者尽力保持了权威的语气,但是这些章节应该被视为一个 Vulkan 初学者的“尽力而为”。

如果你有问题、建议或者修正,请[提交 issue](https://github.com/KyleMayes/vulkanalia/issues)! 8 | 9 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/30_push_constants.rs) | [shader.vert](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/shaders/30/shader.vert) | [shader.frag](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/shaders/30/shader.frag) 10 | 11 | 目前为止我们在教程中创建的场景都是静态的。虽然我们可以操作提供模型、视图和投影(MVP)矩阵的 uniform 缓冲来旋转和移动模型,但是我们不能改变*渲染什么*。这是因为程序在初始化的过程中分配和记录指令缓冲的时候就已经决定了要渲染什么。 12 | 13 | 在接下来的几章中,我们将会探索多种用于渲染动态场景的技巧。我们首先来看一下*推送常量*,这是 Vulkan 的一个特性,它允许我们轻松高效地“推送”动态数据到着色器。仅靠推送常量本身并不能实现动态场景,但是在接下来的几章中,你会逐渐明白它的用处。 14 | 15 | ## 推式常量与 uniform 缓冲 16 | 17 | 之前我们已经在使用另一种能将动态数据提供给顶点着色器的 Vulkan 特性了:那就是 uniform 缓冲。在渲染每一帧时,`App::update_uniform_buffer` 方法都会根据模型当前的旋转角度计算出新的 MVP 矩阵,并将这些矩阵复制到 uniform 缓冲中。然后顶点着色器从 uniform 缓冲中读取这些矩阵,以确定模型的顶点在屏幕上的位置。 18 | 19 | 目前为止这种方法工作得很好,那么为什么我们要用推送常量取而代之呢?相比于 uniform 缓冲,推送常量的优势之一是速度:更新推送常量比复制新数据到 uniform 缓冲要快得多。如果有大量需要频繁更新的值,这种差异会很快累积起来。 20 | 21 | 当然,这里有一个限制:使用推送常量提供给着色器的数据量有一个*非常*有限的最大值。这个最大值因设备而异,由 `vk::PhysicalDeviceLimits` 的 `max_push_constants_size` 字段以字节为单位指定。Vulkan 要求这个限制至少为[128字节](https://www.khronos.org/registry/vulkan/specs/1.2/html/chap33.html#limits-minmax)(见表 32),但实际设备上的最大值也不会比 128 高多少。即使是 RTX 3080 这样的高端硬件,这个最大值也只有 256 字节。 22 | 23 | 如果我们想用推送常量来向矩阵提供我们的 MVP 矩阵,那么我们马上就会撞到这个限制。MVP 矩阵太大了,无法被可靠地放入推送常量中:每个矩阵是 64 字节(16 × 4 字节浮点数),总共 192 字节。当然,我们可以写两套代码,一套用于处理推送常量 >= 192 字节的设备,另一套用于处理无法满足这个需求的设备,但是我们还有更简单的方法。 24 | 25 | 一种方法就是将 MVP 矩阵预先相乘,获得一个单独的矩阵。另一种方法是只将模型矩阵作为推送常量提供,而将视图和投影矩阵留在 uniform 缓冲中。这两种方法都可以为我们提供至少 64 字节的余量。在本章中,我们将采用第二种方法来探索推送常量。 26 | 27 | 为什么只将推送常量用于模型矩阵呢?在 `App::update_uniform_buffer` 方法中,你可以注意到 `model` 矩阵随着 `time` 的增加,在每一帧中都会有所改变,而 `view` 矩阵是静态的,`proj` 矩阵则只会在窗口大小发生变化的时候改变。这就使得我们可以只在窗口大小发生变化时更新包含视图和投影矩阵的 uniform 缓冲,而将推送常量用于不断变化的模型矩阵。 28 | 29 | 当然,在实际的应用程序中,视图矩阵不太可能是静态的。例如,如果你在构建一个第一人称游戏,`view` 矩阵可能会随着玩家在游戏世界中移动而快速变化。然而,即使视图和投影矩阵每一帧都发生改变,至少它们也会在你渲染的所有模型之间共享。也就是说你可以在每一帧中更新 uniform 缓冲,以提供共享的视图和投影矩阵,并使用推送常量来为场景中每个模型提供模型矩阵。 30 | 31 | ## 推送模型矩阵 32 | 33 | 文字部分就到这里,现在让我们开始吧,将顶点着色器中的模型矩阵从 uniform 缓冲对象移动到推送常量中。别忘了重新编译顶点着色器! 34 | 35 | ```glsl 36 | #version 450 37 | 38 | layout(binding = 0) uniform UniformBufferObject { 39 | mat4 view; 40 | mat4 proj; 41 | } ubo; 42 | 43 | layout(push_constant) uniform PushConstants { 44 | mat4 model; 45 | } pcs; 46 | 47 | // ... 48 | 49 | void main() { 50 | gl_Position = ubo.proj * ubo.view * pcs.model * vec4(inPosition, 1.0); 51 | // ... 52 | } 53 | ``` 54 | 55 | 注意不同于 uniform 缓冲对象,推送常量的布局是 `push_constant`,而不是像 `push_constant = 0` 这样的东西。这是因为我们在调用图形管线时只能提供一个推送常量集合,并且如上所述,这个集合非常有限。 56 | 57 | 从 `UniformBufferObject` 结构体中移除 `model`,因为从现在开始我们会为模型矩阵使用推送常量。 58 | 59 | ```rust,noplaypen 60 | #[repr(C)] 61 | #[derive(Copy, Clone, Debug)] 62 | struct UniformBufferObject { 63 | view: Mat4, 64 | proj: Mat4, 65 | } 66 | ``` 67 | 68 | Also remove `model` from the `App::update_command_buffers` method. 69 | 70 | 同时从 `App::update_command_buffers` 方法中移除 `model`。 71 | 72 | ```rust,noplaypen 73 | let view = Mat4::look_at_rh( 74 | point3(2.0, 2.0, 2.0), 75 | point3(0.0, 0.0, 0.0), 76 | vec3(0.0, 0.0, 1.0), 77 | ); 78 | 79 | let correction = Mat4::new( 80 | 1.0, 0.0, 0.0, 0.0, 81 | 0.0, -1.0, 0.0, 0.0, 82 | 0.0, 0.0, 1.0 / 2.0, 0.0, 83 | 0.0, 0.0, 1.0 / 2.0, .0, 84 | ); 85 | 86 | let proj = correction * cgmath::perspective( 87 | Deg(45.0), 88 | self.data.swapchain_extent.width as f32 / self.data.swapchain_extent.height as f32, 89 | 0.1, 90 | 10.0, 91 | ); 92 | 93 | let ubo = UniformBufferObject { view, proj }; 94 | 95 | // ... 96 | ``` 97 | 98 | 我们需要更新图形管线的布局,将我们的新推送常量告知 Vulkan。在 `create_pipeline` 函数中,你可以看到我们之前已经将描述符集合布局提供给了 `create_pipeline_layout`。描述符集合布局描述了我们着色器中使用的 uniform 缓冲对象和纹理采样器,而现在我们需要使用推送常量范围 `vk::PushConstantRange` 来描述图形管线中着色器将要访问的推送常量。 99 | 100 | ```rust,noplaypen 101 | let vert_push_constant_range = vk::PushConstantRange::builder() 102 | .stage_flags(vk::ShaderStageFlags::VERTEX) 103 | .offset(0) 104 | .size(64 /* 16 × 4 byte floats */); 105 | 106 | let set_layouts = &[data.descriptor_set_layout]; 107 | let push_constant_ranges = &[vert_push_constant_range]; 108 | let layout_info = vk::PipelineLayoutCreateInfo::builder() 109 | .set_layouts(set_layouts) 110 | .push_constant_ranges(push_constant_ranges); 111 | 112 | data.pipeline_layout = device.create_pipeline_layout(&layout_info, None)?; 113 | ``` 114 | 115 | 这里的推送常量范围说明了顶点着色器访问的推送常量可以在提供给图形管线的推送常量的开头找到,大小相当于一个 `mat4`。 116 | 117 | 有了这些,我们就可以开始将模型矩阵推送到顶点着色器了。推送常量会被直接记录到提交给 GPU 的指令缓冲中,这就是为什么它们如此快速,这也解释了为什么它们的大小如此有限。 118 | 119 | 在 `create_command_buffers` 函数中,定义一个模型矩阵,并在我们记录绘制指令之前,使用 `cmd_push_constants` 将其作为推送常量添加到指令缓冲中。 120 | 121 | ```rust,noplaypen 122 | let model = Mat4::from_axis_angle( 123 | vec3(0.0, 0.0, 1.0), 124 | Deg(0.0) 125 | ); 126 | 127 | let model_bytes = std::slice::from_raw_parts( 128 | &model as *const Mat4 as *const u8, 129 | size_of::() 130 | ); 131 | 132 | for (i, command_buffer) in data.command_buffers.iter().enumerate() { 133 | // ... 134 | 135 | device.cmd_push_constants( 136 | *command_buffer, 137 | data.pipeline_layout, 138 | vk::ShaderStageFlags::VERTEX, 139 | 0, 140 | model_bytes, 141 | ); 142 | device.cmd_draw_indexed(*command_buffer, data.indices.len() as u32, 1, 0, 0, 0); 143 | 144 | // ... 145 | } 146 | ``` 147 | 148 | 如果你现在运行程序,你会看到熟悉的模型,但是它不再旋转了!我们不再在每一帧中更新 uniform 缓冲对象中的模型矩阵,而是将其编码到指令缓冲中,正如之前所讨论的,指令缓冲永远不会被更新。这进一步凸显了以某种方式更新指令缓冲的需求,这个话题将在下一章中讨论。现在,让我们在片元着色器中再添加一个推送常量,然后结束本章。 149 | 150 | ## 推送透明度 151 | 152 | 接下来我们将在片元着色器中添加一个推送常量,我们可以用它来控制模型的不透明度。首先修改片元着色器,将推送常量包含进去,并将其用作片元颜色的 alpha 通道。同样,一定要重新编译着色器! 153 | 154 | ```glsl 155 | #version 450 156 | 157 | layout(binding = 1) uniform sampler2D texSampler; 158 | 159 | layout(push_constant) uniform PushConstants { 160 | layout(offset = 64) float opacity; 161 | } pcs; 162 | 163 | // ... 164 | 165 | void main() { 166 | outColor = vec4(texture(texSampler, fragTexCoord).rgb, pcs.opacity); 167 | } 168 | ``` 169 | 170 | 这次我们为推送常量值指定了一个偏移量。记住,推送常量在图形管线中的所有着色器之间是共享的,所以我们需要考虑到推送常量的前 64 字节已经被顶点着色器中使用的模型矩阵占用了。 171 | 172 | 在管线布局中为新的不透明度推送常量添加一个推送常量范围。 173 | 174 | ```rust,noplaypen 175 | let vert_push_constant_range = vk::PushConstantRange::builder() 176 | .stage_flags(vk::ShaderStageFlags::VERTEX) 177 | .offset(0) 178 | .size(64 /* 16 × 4 byte floats */); 179 | 180 | let frag_push_constant_range = vk::PushConstantRange::builder() 181 | .stage_flags(vk::ShaderStageFlags::FRAGMENT) 182 | .offset(64) 183 | .size(4); 184 | 185 | let set_layouts = &[data.descriptor_set_layout]; 186 | let push_constant_ranges = &[vert_push_constant_range, frag_push_constant_range]; 187 | let layout_info = vk::PipelineLayoutCreateInfo::builder() 188 | .set_layouts(set_layouts) 189 | .push_constant_ranges(push_constant_ranges); 190 | 191 | data.pipeline_layout = device.create_pipeline_layout(&layout_info, None)?; 192 | ``` 193 | 194 | 最后,在 `create_command_buffers` 中,在为模型矩阵调用 `cmd_push_constants` 之后,再添加一个 `cmd_push_constants` 的调用。 195 | 196 | ```rust,noplaypen 197 | device.cmd_push_constants( 198 | *command_buffer, 199 | data.pipeline_layout, 200 | vk::ShaderStageFlags::VERTEX, 201 | 0, 202 | model_bytes, 203 | ); 204 | device.cmd_push_constants( 205 | *command_buffer, 206 | data.pipeline_layout, 207 | vk::ShaderStageFlags::FRAGMENT, 208 | 64, 209 | &0.25f32.to_ne_bytes()[..], 210 | ); 211 | device.cmd_draw_indexed(*command_buffer, data.indices.len() as u32, 1, 0, 0, 0); 212 | ``` 213 | 214 | 这里我们通过在模型矩阵的 64 字节之后将不透明度 `0.25` 记录到指令缓冲中,来向片元着色器提供不透明度。然而,如果你现在运行程序,你会发现模型仍然完全不透明! 215 | 216 | 回看[固定功能](../pipeline/fixed_functions.html#color-blending)那一章,我们讨论过如果要渲染透明几何体,就需要设置阿尔法合成。然而,那时我们禁用了阿尔法合成。所以现在我们要在 `create_pipeline` 函数中,更新 `vk::PipelineColorBlendAttachmentState`,以启用阿尔法合成,就像那一章中描述的那样。 217 | 218 | ```rust,noplaypen 219 | let attachment = vk::PipelineColorBlendAttachmentState::builder() 220 | .color_write_mask(vk::ColorComponentFlags::all()) 221 | .blend_enable(true) 222 | .src_color_blend_factor(vk::BlendFactor::SRC_ALPHA) 223 | .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA) 224 | .color_blend_op(vk::BlendOp::ADD) 225 | .src_alpha_blend_factor(vk::BlendFactor::ONE) 226 | .dst_alpha_blend_factor(vk::BlendFactor::ZERO) 227 | .alpha_blend_op(vk::BlendOp::ADD); 228 | ``` 229 | 230 | 运行程序,看看我们现在幽灵一般的模型。 231 | 232 | ![](../images/opacity_push_constant.png) 233 | 234 | 成功了! 235 | -------------------------------------------------------------------------------- /book/src/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | 本页面列举了在开发 Vulkan 应用时可能遇到的常见问题及其解决方案。 8 | 9 | * **(MacOS)** 我安装了 Vulkan SDK,但我运行 Vulkan 应用程序的时候遇到了找不到 `libvulkan.dylib` 的错误 —— 参见[MacOS Vulkan SDK 安装说明中的`环境配置`章节](./development_environment.md#setup-environment)。 10 | 11 | * **我在核心校验层中遇到了访问冲突(access violation)错误** – 确保未运行 MSI Afterburner / RivaTuner Statistics Server,因为它们和 Vulkan 之间存在一些兼容性问题。 12 | 13 | * **我看不到任何来自校验层的消息/校验层不可用** – 首先确保校验层有机会打印错误信息,请在程序退出后保持终端窗口打开。在 Visual Studio 中,你可以通过使用 Ctrl-F5 而不是 F5 来运行程序;在 Linux 中,你可以从终端窗口执行程序。如果仍然没有消息,并且你确信校验层已启用,那么你应该按照[此页面上的“Verify the Installation”说明](https://vulkan.lunarg.com/doc/view/1.2.135.0/windows/getting_started.html)来确保 Vulkan SDK 已正确安装。同时确保你的 SDK 版本至少为 1.1.106.0,以支持 `VK_LAYER_KHRONOS_validation` 校验层。 14 | 15 | * **`vkCreateSwapchainKHR` 在 `SteamOverlayVulkanLayer64.dll` 中引发错误** – 这似乎是测试版 Steam 客户端中的一个兼容性问题。以下有几个也许可行的解决方法: 16 | * 退出 Steam 测试计划 17 | * 将环境变量 `DISABLE_VK_LAYER_VALVE_steam_overlay_1` 设置为`1`。 18 | * 删除注册表中 `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers` 下的 Steam overlay Vulkan layer 项目。 19 | 20 | 示例: 21 | 22 | ![](./images/steam_layers_env.png) 23 | -------------------------------------------------------------------------------- /book/src/images/3_models.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/3_models.png -------------------------------------------------------------------------------- /book/src/images/4_models.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/4_models.png -------------------------------------------------------------------------------- /book/src/images/aliasing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/aliasing.png -------------------------------------------------------------------------------- /book/src/images/anisotropic_filtering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/anisotropic_filtering.png -------------------------------------------------------------------------------- /book/src/images/antialiasing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/antialiasing.png -------------------------------------------------------------------------------- /book/src/images/cube_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/cube_demo.png -------------------------------------------------------------------------------- /book/src/images/cube_demo_mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/cube_demo_mac.png -------------------------------------------------------------------------------- /book/src/images/cube_demo_nowindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/cube_demo_nowindow.png -------------------------------------------------------------------------------- /book/src/images/depth_correct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/depth_correct.png -------------------------------------------------------------------------------- /book/src/images/depth_issues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/depth_issues.png -------------------------------------------------------------------------------- /book/src/images/drawing_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/drawing_model.png -------------------------------------------------------------------------------- /book/src/images/highmipmaps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/highmipmaps.png -------------------------------------------------------------------------------- /book/src/images/i_have_no_idea_what_im_doing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/i_have_no_idea_what_im_doing.jpg -------------------------------------------------------------------------------- /book/src/images/indexed_rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/indexed_rectangle.png -------------------------------------------------------------------------------- /book/src/images/inverted_texture_coordinates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/inverted_texture_coordinates.png -------------------------------------------------------------------------------- /book/src/images/mipmaps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/mipmaps.png -------------------------------------------------------------------------------- /book/src/images/mipmaps_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/mipmaps_comparison.png -------------------------------------------------------------------------------- /book/src/images/mipmaps_comparison_axe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/mipmaps_comparison_axe.png -------------------------------------------------------------------------------- /book/src/images/mipmaps_example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/mipmaps_example.jpg -------------------------------------------------------------------------------- /book/src/images/multisampling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/multisampling.png -------------------------------------------------------------------------------- /book/src/images/multisampling_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/multisampling_comparison.png -------------------------------------------------------------------------------- /book/src/images/multisampling_comparison_axe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/multisampling_comparison_axe.png -------------------------------------------------------------------------------- /book/src/images/opacity_push_constant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/opacity_push_constant.png -------------------------------------------------------------------------------- /book/src/images/sample_shading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/sample_shading.png -------------------------------------------------------------------------------- /book/src/images/semaphore_in_use.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/semaphore_in_use.png -------------------------------------------------------------------------------- /book/src/images/spinning_ghost_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/spinning_ghost_model.png -------------------------------------------------------------------------------- /book/src/images/spinning_quad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/spinning_quad.png -------------------------------------------------------------------------------- /book/src/images/steam_layers_env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/steam_layers_env.png -------------------------------------------------------------------------------- /book/src/images/swapchain_validation_layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/swapchain_validation_layer.png -------------------------------------------------------------------------------- /book/src/images/texcoord_visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/texcoord_visualization.png -------------------------------------------------------------------------------- /book/src/images/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/texture.png -------------------------------------------------------------------------------- /book/src/images/texture_addressing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/texture_addressing.png -------------------------------------------------------------------------------- /book/src/images/texture_filtering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/texture_filtering.png -------------------------------------------------------------------------------- /book/src/images/texture_on_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/texture_on_square.png -------------------------------------------------------------------------------- /book/src/images/texture_on_square_colorized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/texture_on_square_colorized.png -------------------------------------------------------------------------------- /book/src/images/texture_on_square_repeated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/texture_on_square_repeated.png -------------------------------------------------------------------------------- /book/src/images/triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/triangle.png -------------------------------------------------------------------------------- /book/src/images/triangle_coordinates.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 56 | 61 | 68 | 74 | v0 85 | v1 96 | v2 107 | 108 | 109 | -------------------------------------------------------------------------------- /book/src/images/triangle_coordinates_colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/triangle_coordinates_colors.png -------------------------------------------------------------------------------- /book/src/images/triangle_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/triangle_white.png -------------------------------------------------------------------------------- /book/src/images/validation_layer_anisotropy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/validation_layer_anisotropy.png -------------------------------------------------------------------------------- /book/src/images/validation_layer_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/validation_layer_test.png -------------------------------------------------------------------------------- /book/src/images/viewports_scissors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/viewports_scissors.png -------------------------------------------------------------------------------- /book/src/images/viking_room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/viking_room.png -------------------------------------------------------------------------------- /book/src/images/vulkan_sdk_download_buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/src/images/vulkan_sdk_download_buttons.png -------------------------------------------------------------------------------- /book/src/introduction.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | 本教程是用 Rust 对 的改写版本,应当归功于原教程的作者 ([Alexander Overvoorde](https://github.com/Overv)) 及[其他贡献者们](https://github.com/Overv/VulkanTutorial/graphs/contributors)。 8 | 9 | 同时,本教程也包含由笔者原创的章节(从[推送常量](./dynamic/push_constants.html)章节开始)。这些章节介绍了在几乎所有的 Vulkan 应用中都非常重要的 Vulkan 概念和特性。然而,正如这些章节中所说明的那样,这些特性是实验性的。 10 | 11 | ## 关于 12 | 13 | 本教程会教授一些 [Vulkan](https://khronos.org/vulkan) 图形与计算 API 的基础知识。Vulkan 是一个由 [Khronos 组织](https://www.khronos.org/) (因 OpenGL 而为人所知)提出的新 API,针对现代显卡的特性提供了更好的抽象。新的接口可以让你更好地描述你的应用程序要做什么,从而带来相比于 [OpenGL](https://en.wikipedia.org/wiki/OpenGL) 和 [Direct3D](https://en.wikipedia.org/wiki/Direct3D) 之类的现有的图形 API 更好的性能和更少的意外驱动程序行为。Vulkan 的设计思想与 [Direct3D 12](https://en.wikipedia.org/wiki/Direct3D#Direct3D_12) 和 [Metal](https://en.wikipedia.org/wiki/Metal_(API)) 相似,但 Vulkan 在跨平台方面具有优势,可以让你同时开发 Windows,Linux 和 Android 应用程序(并借由 [MoltenVK](https://github.com/KhronosGroup/MoltenVK) 开发 iOS 与 MacOS 应用程序)。 14 | 15 | 然而,为了这些增益,你所付出的代价便是你要使用一个更加冗长的 API。从创建帧缓冲(framebuffer)到管理缓冲和纹理图像一类对象的内存,每个和图形 API 相关的细节都需要在你的应用程序中从头开始设置。图形驱动程序会做更少手把手的指导,也就意味着你要在你的应用程序中做更多工作来确保正确的行为。 16 | 17 | 简而言之,Vulkan 并不是适合所有人使用的 API。它面向的是那些热衷于高性能计算机图形学,并且愿意为其投入精力的程序员们。如果你更感兴趣的是游戏开发而不是计算机图形学,那么你可能还是应该坚持使用 OpenGL 或者 Direct3D,因为它们不会那么快被 Vulkan 取代。另一个选择是使用像 [Unreal Engine](https://en.wikipedia.org/wiki/Unreal_Engine#Unreal_Engine_4) 这样的引擎,它们可以使用 Vulkan,但会提供给你一个更高层次的 API。 18 | 19 | 抛开上面的问题,让我们来看看跟着这个教程学习所需的一些东西: 20 | 21 | * 一张支持 Vulkan 的显卡和驱动程序([NVIDIA](https://developer.nvidia.com/vulkan-driver),[AMD](http://www.amd.com/en-us/innovations/software-technologies/technologies-gaming/vulkan),[Intel](https://software.intel.com/en-us/blogs/2016/03/14/new-intel-vulkan-beta-1540204404-graphics-driver-for-windows-78110-1540)) 22 | * 使用 Rust 的经验 23 | * Rust 1.70 或更高版本 24 | * 一些的 3D 计算机图形学知识 25 | 26 | 本教程不会要求你有 OpenGL 或者 Direct3D 的知识储备,但要求你必须理解 3D 计算机图形学的基础。例如,教程中不会解释透视投影背后的数学原理。[这本在线书籍](https://paroj.github.io/gltut/)是一个很好的计算机图形学入门资源。其他一些很好的计算机图形学资源包括: 27 | 28 | * [Ray tracing in one weekend](https://raytracing.github.io/books/RayTracingInOneWeekend.html) 29 | * [Physically Based Rendering book](http://www.pbr-book.org/) 30 | * 在真正的开源游戏引擎中使用 Vulkan:[Quake](https://github.com/Novum/vkQuake) 和 [DOOM 3](https://github.com/DustinHLand/vkDOOM3) 31 | 32 | > 私货:本文的译者还写过两篇“十分钟计算机图形学”([其一](https://self.icey.tech/#/blog/1690381815-10-minutes-computer-graphics),[其二](https://self.icey.tech/#/blog/1690614312-10-minutes-computer-graphics-2)) 33 | 34 | 如果你想要 C++ 教程,请查看原教程:
35 | 36 | 本教程使用 [`vulkanalia`](https://github.com/KyleMayes/vulkanalia) crate 来提供 Rust 语言对 Vulkan API 的访问。`vulkanalia` 提供了对 Vulkan API 的原始绑定,同时也提供了一个轻量级的封装,使得运用这些 API 更简单,也“更 Rust”(下一章里你会看到的)。也就是说,你不必费心考虑你的 Rust 程序如何与 Vulkan API 交互,同时你也能免受 Vulkan API 的危险性和冗长性的影响。 37 | 38 | 如果你想要一个使用更安全、细致封装的 [`vulkano`](https://vulkano.rs) crate 的 Rust Vulkan 教程,请查看这个教程:。 39 | 40 | ## 教程结构 41 | 42 | 我们首先会速览一下 Vulkan 是如何工作的,以及要把第一个三角形画到屏幕上所需的工作。在你了解这些小的步骤在整个过程中的基本作用之后,它们的目的会更加清晰。接下来,我们会使用 [Vulkan SDK](https://lunarg.com/vulkan-sdk/) 来设置开发环境。 43 | 44 | 在这之后,我们会实现渲染第一个三角形的 Vulkan 程序所需的所有基本组件。每一章的结构大致如下: 45 | 46 | * 引入一个新的概念及其目的 47 | * 使用所有相关的 API 调用,将其集成到你的程序中 48 | * 将其抽象为辅助函数 49 | 50 | 尽管每一章都是作为前一章的后续章节编写的,但把每一章作为单独的介绍一个特定 Vulkan 特性的文章来阅读也是可以的。也就是说这个网站也可以作为一个 Vulkan 参考。所有 Vulkan 函数和类型都链接到了 Vulkan 规范或者 `vulkanalia` 的文档,你可以点击链接来了解更多。Vulkan 仍然是一个非常年轻的 API,所以 Vulkan 的规范本身可能有一些缺点。你可以提交反馈到 [这个 Khronos 仓库](https://github.com/KhronosGroup/Vulkan-Docs)。 51 | 52 | 如同前面所提到的,Vulkan API 是一个非常冗长的 API,提供了许多参数,能给你对图形硬件最大的控制。这就导致像创建纹理这种基本操作都要经过很多步骤,而且每次都要重复。因此,我们会在整个教程中会创建一系列我们自己的的辅助函数。 53 | 54 | 每一章也包含了一个链接,指向了该章节完成后的最终代码。如果你对代码的结构有任何疑问,或者你遇到了 bug,想要对比一下,你可以参考这些代码。 55 | 56 | 本教程旨在成为社区的共同努力。Vulkan 仍然是一个相当新的 API,而最佳实践尚未完全建立。如果你对教程或者网站本身有任何类型的反馈,请随时向 [GitHub 存储库](https://github.com/KyleMayes/vulkanalia) 提交问题或拉取请求。 57 | 58 | > 译者注:本译本也有 [GitHub 仓库](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN),如果你对中文翻译有任何疑问或者改进建议,欢迎提交 Issue 和 PR :) 59 | 60 | 在你完成了第一个 Vulkan 三角形的绘制之后,我们会开始扩展程序,添加线性变换、纹理和 3D 模型等功能。 61 | 62 | 如果你有使用其他图形 API 的经验,你会明白在第一个三角形在屏幕上显示出来之前可能会有很多步骤。在 Vulkan 中也有很多这样的步骤,但是你会发现每个步骤都很容易理解,而且不会感觉哪一步是多余的。一旦你有了这个基础的三角形,绘制拥有贴图的 3D 模型并不需要太多额外的工作,并且在这之后的每一步都会给你带来更多增益。 63 | 64 | 如果你在阅读本教程时遇到了任何问题,请查阅 FAQ,看看你的问题和解决方案是否已经在里面列出。接下来,你可以在[原教程](https://vulkan-tutorial.com/)对应章节的评论区里找找,看是否有人遇到了相同的问题(如果不是 Rust 特有的问题)。 65 | -------------------------------------------------------------------------------- /book/src/model/loading_models.md: -------------------------------------------------------------------------------- 1 | # 加载模型 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/27_model_loading.rs) 8 | 9 | 现在你的程序已经可以渲染带纹理的 3D 网格了,但当前 `vertices` 和 `indices` 数组中的几何图形有些乏味。在本章中,我们将扩展程序,从一个实际的模型文件中加载顶点和索引,让显卡可以实际地做点事情。 10 | 11 | 许多图形 API 教程都会让读者在这样的章节中编写自己的 OBJ 加载器。但这样做的问题是,更有趣的 3D 应用程序需要许多功能,例如骨骼动画,而 OBJ 格式不支持这些功能。我们*将会*在本章中从 OBJ 模型加载网格数据,但我们将更多地关注将网格数据与程序本身集成,而不是从文件加载它的细节。 12 | 13 | ## 库 14 | 15 | 我们将使用 [`tobj`](https://crates.io/crates/tobj) crate 从 OBJ 文件中加载顶点和面。如果你遵照了“开发环境”那一章中的说明,那么这个依赖应该已经安装好并且可以使用了。 16 | 17 | ## 示例网格 18 | 19 | 在本章中我们不会启用光照,所以最好使用一个纹理上已经烘焙好光照的示例模型。查找这样的模型的一个简单方法是在 [Sketchfab](https://sketchfab.com/) 上寻找 3D 扫描模型。该网站上的许多模型都以 OBJ 格式提供,并且有宽松的许可证。 20 | 21 | 在本教程中,我决定使用 [nigeloh](https://sketchfab.com/nigelgoh) 做的的 [Viking room](https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38) 模型([CC BY 4.0](https://web.archive.org/web/20200428202538/https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38))。我调整了模型的大小和方向,以便将其用作当前几何图形的替代品: 22 | 23 | * [viking_room.obj](../images/viking_room.obj) 24 | * [viking_room.png](../images/viking_room.png) 25 | 26 | > **注意:**本教程中包含的 `.obj` 和 `.png` 文件可能与原始文件不同(并且可能也与[原始 C++ 教程](https://vulkan-tutorial.com)中使用的文件不同)。请确保使用本教程中的文件。 27 | 28 | 你也可以随便使用你自己的模型,但确保它只使用了一个材质(material),并且其尺寸大约为 1.5 x 1.5 x 1.5 个单位。如果它比这个大,那么你将不得不改变视图矩阵。将模型文件和纹理图像放在 `resources` 目录中。 29 | 30 | 更新 `create_texture_image` 以从这个路径加载图像: 31 | 的模型的选项。我们将 `triangulate` 字段设置为 `true`,以确保加载的模型的组件被转换为三角形。这很重要,因为我们的渲染代 32 | ```rust,noplaypen 33 | let image = File::open("resources/viking_room.png")?; 34 | ``` 35 | 36 | 如果要二次确认你的图像文件是正确的,你还可以在 `create_texture_image` 中在解码 PNG 图像后添加以下代码: 37 | 38 | ```rust,noplaypen 39 | if width != 1024 || height != 1024 || reader.info().color_type != png::ColorType::Rgba { 40 | panic!("Invalid texture image."); 41 | } 42 | ``` 43 | 44 | ## 加载顶点和索引 45 | 46 | 现在我们将从模型文件中加载顶点和索引,所以你现在应该删除全局的 `VERTICES` 和 `INDICES` 数组。用 `AppData` 的字段替换它们: 47 | 48 | ```rust,noplaypen 49 | struct AppData { 50 | // ... 51 | vertices: Vec, 52 | indices: Vec, 53 | vertex_buffer: vk::Buffer, 54 | vertex_buffer_memory: vk::DeviceMemory, 55 | // ... 56 | } 57 | ``` 58 | 59 | 你还需要用新的 `AppData` 字段替换对全局数组的所有引用。 60 | 61 | 你应该将索引的类型从 `u16` 改为 `u32`,因为顶点数量会远超过 65,536。记得也要改变 `cmd_bind_index_buffer` 的参数: 62 | 63 | ```rust,noplaypen 64 | device.cmd_bind_index_buffer( 65 | *command_buffer, 66 | data.index_buffer, 67 | 0, 68 | vk::IndexType::UINT32, 69 | ); 70 | ``` 71 | 72 | 你还需要更新 `create_index_buffer` 中索引缓冲的大小: 73 | 74 | ```rust,noplaypen 75 | let size = (size_of::() * data.indices.len()) as u64; 76 | ``` 77 | 78 | 接着我们需要再导入一些东西: 79 | 80 | ```rust,noplaypen 81 | use std::collections::HashMap; 82 | use std::hash::{Hash, Hasher}; 83 | use std::io::BufReader; 84 | ``` 85 | 86 | 我们现在要编写一个 `load_models` 函数,它将使用 `tobj` 库来从网格中获取顶点数据并填充 `vertices` 和 `indices` 字段。它应该在创建顶点和索引缓冲之前的某个地方被调用: 87 | 88 | ```rust,noplaypen 89 | impl App { 90 | unsafe fn create(window: &Window) -> Result { 91 | // ... 92 | load_model(&mut data)?; 93 | create_vertex_buffer(&instance, &device, &mut data)?; 94 | create_index_buffer(&instance, &device, &mut data)?; 95 | // ... 96 | } 97 | } 98 | 99 | fn load_model(data: &mut AppData) -> Result<()> { 100 | Ok(()) 101 | } 102 | ``` 103 | 104 | 调用 `tobj::load_obj_buf` 函数来将模型加载到 `tobj` crate 的数据结构中: 105 | 106 | ```rust,noplaypen 107 | let mut reader = BufReader::new(File::open("resources/viking_room.obj")?); 108 | 109 | let (models, _) = tobj::load_obj_buf( 110 | &mut reader, 111 | &tobj::LoadOptions { triangulate: true, ..Default::default() }, 112 | |_| Ok(Default::default()), 113 | )?; 114 | ``` 115 | 116 | OBJ 文件由位置、法线、纹理坐标和面组成。面由任意数量的顶点组成,其中每个顶点通过索引引用位置、法线和/或纹理坐标。这使得 OBJ 文件中的面不仅可以重用整个顶点,还可以重用顶点的单个属性。 117 | 118 | `tobj::load_obj_buf` 返回一个模型的 `Vec` 和一个材质的 `Vec`。我们对材质不感兴趣,只对模型感兴趣,所以返回的材质被忽略了。 119 | 120 | `tobj::load_obj_buf` 的第二个参数指定了处理加载的模型的选项。我们将 `triangulate` 字段设置为 `true`,以确保加载的模型的组件被转换为三角形。这很重要,因为我们的渲染代码只能处理三角形。我们的 Viking room 模型不需要这个,因为它的面已经是三角形了,但如果你尝试使用不同的 OBJ 文件,这可能是必要的。 121 | 122 | `tobj::load_obj_buf` 的第三个参数是一个回调,用于加载 OBJ 文件中引用的材质。我们对材质不感兴趣,所以我们只返回一个空材质。 123 | 124 | 我们将把文件中的所有面组合成一个模型,所以我们遍历所有模型: 125 | 126 | ```rust,noplaypen 127 | for model in &models { 128 | } 129 | ``` 130 | 131 | 三角化功能已经确保每个面有三个顶点,所以我们现在可以直接遍历顶点并将它们直接转储到我们的 `vertices` 向量中: 132 | 133 | ```rust,noplaypen 134 | for model in &models { 135 | for index in &model.mesh.indices { 136 | let vertex = Vertex { 137 | pos: vec3(0.0, 0.0, 0.0), 138 | color: vec3(1.0, 1.0, 1.0), 139 | tex_coord: vec2(0.0, 0.0), 140 | }; 141 | 142 | data.vertices.push(vertex); 143 | data.indices.push(data.indices.len() as u32); 144 | } 145 | } 146 | ``` 147 | 148 | 简单起见,我们现在假设每个顶点都是唯一的,因此简单地自增索引就行。`index` 变量用于在 `positions` 和 `texcoords` 数组中查找实际的顶点属性: 149 | 150 | ```rust,noplaypen 151 | let pos_offset = (3 * index) as usize; 152 | let tex_coord_offset = (2 * index) as usize; 153 | 154 | let vertex = Vertex { 155 | pos: vec3( 156 | model.mesh.positions[pos_offset], 157 | model.mesh.positions[pos_offset + 1], 158 | model.mesh.positions[pos_offset + 2], 159 | ), 160 | color: vec3(1.0, 1.0, 1.0), 161 | tex_coord: vec2( 162 | model.mesh.texcoords[tex_coord_offset], 163 | model.mesh.texcoords[tex_coord_offset + 1], 164 | ), 165 | }; 166 | ``` 167 | 168 | 不幸的是,`tobj::load_obj_buf` 返回的 `positions` 是一个扁平的数组,存储的是 `f32` 而不是像 `cgmath::Vector3` 这样的东西。考虑到每个顶点坐标有三个分量,你需要将索引乘以 `3`。类似地,每个纹理坐标有两个分量。对于顶点坐标,偏移量 `0`、`1` 和 `2` 会被用于访问 X、Y 和 Z 分量;对于纹理坐标,偏移量 `0` 和 `1` 会被用于访问 U 和 V 分量。 169 | 170 | 你可能想从现在开始在 release 模式下编译你的程序,因为没有优化的情况下加载纹理和模型可能会非常慢。如果你现在运行你的程序,你应该会看到如下所示的东西: 171 | 172 | ![](../images/inverted_texture_coordinates.png) 173 | 174 | 太棒了,几何图形看起来是正确的,但纹理怎么了?OBJ 格式使用这样一个坐标系:垂直坐标 `0` 表示图像的底部。但是我们已经将图像以自上而下的方式上传到 Vulkan 中,其中 `0` 表示图像的顶部。我们通过翻转纹理坐标的垂直分量来解决这个问题: 175 | 176 | ```rust,noplaypen 177 | tex_coord: vec2( 178 | model.mesh.texcoords[tex_coord_offset], 179 | 1.0 - model.mesh.texcoords[tex_coord_offset + 1], 180 | ), 181 | ``` 182 | 183 | 再次运行你的程序,你应该会看到正确的结果: 184 | 185 | ![](../images/drawing_model.png) 186 | 187 | 所有这些辛苦的工作终于开始得到回报了! 188 | 189 | ## 顶点去重 190 | 191 | 不幸的是,我们还没有真正地从索引缓冲中获益。`vertices` 现在包含了大量重复的顶点数据,因为许多顶点都被多个三角形共用。我们应该只保留唯一一个顶点,并使用索引缓冲重用它们。要实现这一点,一种直接的方法是使用 `HashMap` 来跟踪唯一的顶点和相应的索引: 192 | 193 | ```rust,noplaypen 194 | let mut unique_vertices = HashMap::new(); 195 | 196 | for model in &models { 197 | for index in &model.mesh.indices { 198 | // ... 199 | 200 | if let Some(index) = unique_vertices.get(&vertex) { 201 | data.indices.push(*index as u32); 202 | } else { 203 | let index = data.vertices.len(); 204 | unique_vertices.insert(vertex, index); 205 | data.vertices.push(vertex); 206 | data.indices.push(index as u32); 207 | } 208 | } 209 | ``` 210 | 211 | 我们从 OBJ 文件中读取一个索引,并检查我们之前是否已经看到过一个具有完全相同的位置和纹理坐标的顶点。如果没有,我们将它添加到 `vertices` 中,并将其索引存储在 `unique_vertices` 容器中。之后,我们将新顶点的索引添加到 `indices` 中。如果我们之前看到过完全相同的顶点,那么我们将在 `unique_vertices` 中查找它的索引,并将该索引存储在 `indices` 中。 212 | 213 | 程序现在将无法编译,因为我们需要为我们的 `Vertex` 结构实现 `Hash` trait,以便将其用作 `HashMap` 的键。不幸的是,由于 `Vertex` 包含 `f32`,我们需要手动实现 `Hash` 和所需的 trait(`PartialEq` 和 `Eq`)(注意,我们的 `Eq` 实现只在顶点数据中没有 `NaN` 的情况下才有效,这是一个安全的假设)。 214 | 215 | ```rust,noplaypen 216 | impl PartialEq for Vertex { 217 | fn eq(&self, other: &Self) -> bool { 218 | self.pos == other.pos 219 | && self.color == other.color 220 | && self.tex_coord == other.tex_coord 221 | } 222 | } 223 | 224 | impl Eq for Vertex {} 225 | 226 | impl Hash for Vertex { 227 | fn hash(&self, state: &mut H) { 228 | self.pos[0].to_bits().hash(state); 229 | self.pos[1].to_bits().hash(state); 230 | self.pos[2].to_bits().hash(state); 231 | self.color[0].to_bits().hash(state); 232 | self.color[1].to_bits().hash(state); 233 | self.color[2].to_bits().hash(state); 234 | self.tex_coord[0].to_bits().hash(state); 235 | self.tex_coord[1].to_bits().hash(state); 236 | } 237 | } 238 | ``` 239 | 240 | 现在你应该能够成功编译和运行你的程序。如果你检查 `vertices` 的大小,你会发现顶点数量已经从 1,500,000 减少到了 265,645!这意味着每个顶点平均被大约 6 个三角形重用。这绝对节省了大量的 GPU 内存。 241 | -------------------------------------------------------------------------------- /book/src/pipeline/conclusion.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/12_graphics_pipeline_complete.rs) 8 | 9 | 现在我们可以开始组合前几章中创建的所有结构和对象来创建图形管线了!回顾一下我们现在有的对象: 10 | 11 | * 着色器阶段 – 着色器单元定义的图形管线中的可编程阶段 12 | * 固定功能状态 – 管线中所有定义固定功能阶段的结构,例如输入组装、光栅化器、视口和颜色混合 13 | * 管线布局 – 定义着色器引用的可以在绘制时更新的 `uniform` 值和推送常量 14 | * 渲染流程 – 管线阶段中引用的附件,以及它们的用途 15 | 16 | 这些对象定义了图形管线的方方面面。现在我们可以在 `create_pipeline` 函数的末尾(但要在着色器模块销毁之前)开始填充 `vk::GraphicsPipelineCreateInfo` 了。 17 | 18 | ```rust,noplaypen 19 | let stages = &[vert_stage, frag_stage]; 20 | let info = vk::GraphicsPipelineCreateInfo::builder() 21 | .stages(stages) 22 | // continued... 23 | ``` 24 | 25 | 我们首先提供一个 `vk::PipelineShaderStageCreateInfo` 结构体的数组。 26 | 27 | ```rust,noplaypen 28 | .vertex_input_state(&vertex_input_state) 29 | .input_assembly_state(&input_assembly_state) 30 | .viewport_state(&viewport_state) 31 | .rasterization_state(&rasterization_state) 32 | .multisample_state(&multisample_state) 33 | .color_blend_state(&color_blend_state) 34 | ``` 35 | 36 | 接着我们引用所有描述固定功能阶段的结构体。 37 | 38 | ```rust,noplaypen 39 | .layout(data.pipeline_layout) 40 | ``` 41 | 42 | 然后是管线布局。 43 | 44 | ```rust,noplaypen 45 | .render_pass(data.render_pass) 46 | .subpass(0); 47 | ``` 48 | 49 | 最后引用之前创建的渲染流程,以及图形管线将要使用的子流程在子流程数组中的索引。在这个渲染管线上使用其他的渲染流程也是可以的,但这些渲染流程之间必须*相互兼容*。[这里](https://www.khronos.org/registry/vulkan/specs/1.2/html/vkspec.html#renderpass-compatibility)给出了关于兼容性的描述,不过本教程中我们不会使用这个特性。 50 | 51 | ```rust,noplaypen 52 | .base_pipeline_handle(vk::Pipeline::null()) // 可选. 53 | .base_pipeline_index(-1) // 可选. 54 | ``` 55 | 56 | 实际上还有两个参数:`base_pipeline_handle` 和 `base_pipeline_index`。Vulkan 允许你派生一个现有的图形管线来创建新的图形管线。管线派生的意义在于,如果新的管线和旧的管线有很多相似之处,这样做就能减少很多开销;在同一个亲代(parent)派生出的图形管线之间切换也更快。你可以使用 `base_pipeline_handle` 通过句柄来指定一个现有的管线,或者使用 `base_pipeline_index` 通过索引来指定一个即将创建的管线。现在我们只有一个管线,所以我们会简单地指定一个空句柄和一个无效索引。只有在 `vk::GraphicsPipelineCreateInfo` 的 `flags` 字段中也指定了 `vk::PipelineCreateFlags::DERIVATIVE` 标志时,这些值才会被使用。 57 | 58 | 现在,在 `AppData` 中添加一个字段来存储 `vk::Pipeline` 对象: 59 | 60 | ```rust,noplaypen 61 | struct AppData { 62 | // ... 63 | pipeline: vk::Pipeline, 64 | } 65 | ``` 66 | 67 | 然后在 `App::create` 中创建图形管线: 68 | 69 | ```rust,noplaypen 70 | data.pipeline = device.create_graphics_pipelines( 71 | vk::PipelineCache::null(), &[info], None)?.0[0]; 72 | ``` 73 | 74 | `create_graphics_pipelines` 函数的参数比 Vulkan 中通常的对象创建函数要多。它被设计为可以一次性接受多个 `vk::GraphicsPipelineCreateInfo` 对象并创建多个 `vk::Pipeline` 对象。 75 | 76 | 第一个参数是一个对 `vk::PipelineCache` 的引用,这个参数是可选的,我们为其传递 `vk::PipelineCache::null()`。管线缓存可以用来在多次调用 `create_graphics_pipelines` 时存储和重用管线创建相关的数据,甚至可以在程序执行结束后从文件中读取缓存。这样可以显著提高管线创建的速度。 77 | 78 | 图形管线会在所有的绘制操作中使用,所以它也应该在 `App::destroy` 中被销毁: 79 | 80 | ```rust,noplaypen 81 | unsafe fn destroy(&mut self) { 82 | self.device.destroy_pipeline(self.data.pipeline, None); 83 | // ... 84 | } 85 | ``` 86 | 87 | 现在运行程序,来确定*我们一直以来的努力并非全部白费*。现在,我们离看到屏幕上有东西出现不远了。在接下来的几章中,我们将设置交换链图像的实际帧缓冲,并准备绘制指令。 88 | -------------------------------------------------------------------------------- /book/src/pipeline/introduction.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/08_graphics_pipeline.rs) 8 | 9 | 在接下来的几章中,我们会搭建图形管线。它会被用来画我们的第一个三角形。图形管线是一系列将我们提交的顶点与网格纹理转换为渲染目标中的像素点的操作。下面是一个简化版的流程图: 10 | 11 | ![](../images/vulkan_simplified_pipeline.svg) 12 | 13 | *输入装配器*(input assembler)从指定缓冲中收集原始顶点数据,并且也可以使用顶点缓冲来重复使用某些元素,而不需要复制那些元素。 14 | 15 | *顶点着色器*(vertex shader)对每个顶点执行,将变换应用于顶点,例如将其顶点位置从模型空间转换至屏幕空间,然后将每个顶点的数据传输至图形管线的下一阶段。 16 | 17 | *曲面细分着色器*(tessellation shader)使你能够根据特定规则细分几何图形,以提升网格(mesh)的质量。这通常用于使砖墙和楼梯之类的表面从附近看上去更粗糙。 18 | 19 | *几何着色器*(geometry shader)以图元(primitive,例如三角形、线、点)为单位处理几何图形。它可以剔除图元或输出更多图元。这与曲面细分着色器类似,但更灵活。然而如今的程序很少使用几何着色器,因为它在 Intel 集成显卡之外的大部分显卡上性能不佳。 20 | 21 | *光栅化*(rasterization)阶段将图元离散化(discretize)为*片元*(fragment)。片元用来在帧缓冲上填充像素。任何在屏幕外的片元会被丢弃,顶点着色器输出的属性会在片元之间进行插值,如上图所示。在经过深度测试(depth test)后,位于其它图元后面的片元也会被舍弃。 22 | 23 | *片元着色器*(fragment shader)对每一个违背丢弃的片元执行。它会判断哪些片元要写入哪一些帧缓冲,并计算它们的颜色与深度值。它可以使用顶点着色器返回的插值后的数据,例如纹理坐标以及顶点的法线等。 24 | 25 | *混色*(color blending)阶段会把在帧缓冲中同一个像素位置的不同片元进行混合。片元可以简单地覆盖彼此,也可以叠加或根据透明度混合。 26 | 27 | 标为绿色的阶段被称为*固定功能*(fixed-function)阶段。这些阶段所执行的工作是预定义的,但你可以通过参数对处理过程进行一定程度的配置。 28 | 29 | 标为橙色的阶段是*可编程的*(programmable)。你可以将自己的代码上传至显卡,并使它执行想要的操作。例如,你可以使用片元着色器来实现纹理、光照,甚至是光追。这些程序会在显卡的多个核心中同时执行来并行处理多个对象,例如顶点与片元。 30 | 31 | 如果你用过更早的 API,例如 OpenGL 和 Direct3D,你可能会对 `glBlendFunc` 或 `OMSetBlendState` 之类用于修改管线设置的函数比较熟悉。而 Vulkan 的图形管线几乎是完全不可变的,所以如果你想对渲染器进行修改,绑定其它帧缓冲,或是修改混合函数,那你必须重新创建整个管线。这么做的劣势在于你需要创建多个管线来满足你渲染所需的所有不同状态的组合。但是因为你在管线上所做的所有操作都已经事先可知,驱动就可以更好地优化你的管线。 32 | 33 | 根据需求不同,一些可编程阶段是可选的。比如,如果你只是想画简单的几何图形,那么密铺和几何着色器阶段是可以被禁用的。如果你只关心深度值,那你可以禁用片元着色器阶段。这在[阴影贴图](https://en.wikipedia.org/wiki/Shadow_mapping)的生成上很有用。 34 | 35 | 在下一章中,我们会先创建显示三角形所必须的两个可编程阶段:顶点着色器与片元着色器。混合模式、视口、光栅化之类的固定功能的配置会在下一章中介绍。最后我们配置 Vulkan 渲染管线的最后一部分 —— 指定输入与输出的帧缓冲。 36 | 37 | 我们先创建一个 `create_pipeline` 函数,并且在 `App::create` 中调用 `create_swapchain_image_views` 后立刻调用新创建的 `create_pipeline` 函数。我们会在之后几章中修改并实现这个函数。 38 | 39 | ```rust,noplaypen 40 | impl App { 41 | unsafe fn create(window: &Window) -> Result { 42 | // ... 43 | create_swapchain_image_views(&device, &mut data)?; 44 | create_pipeline(&device, &mut data)?; 45 | // ... 46 | } 47 | } 48 | 49 | unsafe fn create_pipeline(device: &Device, data: &mut AppData) -> Result<()> { 50 | Ok(()) 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /book/src/pipeline/render_passes.md: -------------------------------------------------------------------------------- 1 | # 渲染流程 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码** [main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/11_render_passes.rs) 8 | 9 | 在创建渲染管线之前,我们还需要设置渲染过程中将会使用的帧缓冲附件(framebuffer attachments)。我们需要指定有多少个颜色缓冲和深度缓冲,每个缓冲使用多少样本数,以及渲染操作将如何处理缓冲中的内容。所有这些信息都会被装进一个*渲染流程*对象中。我们将会创建一个新的函数 `create_render_pass`,并在 `App::create` 中 `create_pipeline` 之前调用它: 10 | 11 | ```rust,noplaypen 12 | impl App { 13 | unsafe fn create(window: &Window) -> Result { 14 | // ... 15 | create_render_pass(&instance, &device, &mut data)?; 16 | create_pipeline(&device, &mut data)?; 17 | // ... 18 | } 19 | } 20 | 21 | unsafe fn create_render_pass( 22 | instance: &Instance, 23 | device: &Device, 24 | data: &mut AppData, 25 | ) -> Result<()> { 26 | Ok(()) 27 | } 28 | ``` 29 | 30 | ## 附件描述 31 | 32 | 在我们的场景中,我们只需要一个颜色附件。我们会在 `create_render_pass` 函数中创建一个 `vk::AttachmentDescription` 来表示它: 33 | 34 | ```rust,noplaypen 35 | let color_attachment = vk::AttachmentDescription::builder() 36 | .format(data.swapchain_format) 37 | .samples(vk::SampleCountFlags::_1) 38 | // continued... 39 | ``` 40 | 41 | 颜色附件的 `format` 字段需要与交换链图像的格式匹配。我们现在不会进行多重采样,所以我们只用 1 个样本。 42 | 43 | ```rust,noplaypen 44 | .load_op(vk::AttachmentLoadOp::CLEAR) 45 | .store_op(vk::AttachmentStoreOp::STORE) 46 | ``` 47 | 48 | `load_op` 和 `store_op` 决定在渲染之前和之后对附件中的数据做什么。对于 `load_op` 我们有以下三种选择: 49 | 50 | * `vk::AttachmentLoadOp::LOAD` – 保留附件中已有的内容 51 | * `vk::AttachmentLoadOp::CLEAR` – 在渲染开始前将附件清空,为每个像素设置一个常量值 52 | * `vk::AttachmentLoadOp::DONT_CARE` – 附件中已有的内容是未定义的,我们不关心它们 53 | 54 | 在我们的场景下,我们希望在渲染新帧之前将帧缓冲清空为黑色。而 `store_op` 只有两种选择: 55 | 56 | * `vk::AttachmentStoreOp::STORE` – 渲染的内容将会被存储起来,以便之后读取 57 | * `vk::AttachmentStoreOp::DONT_CARE` – 渲染结束后帧缓冲中的内容是未定义的 58 | 59 | 我们希望在屏幕上看到渲染出来的三角形,所以我们选择 `STORE`。 60 | 61 | ```rust,noplaypen 62 | .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE) 63 | .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE) 64 | ``` 65 | 66 | `load_op` 和 `store_op` 对颜色和深度数据生效,而 `stencil_load_op` 和 `stencil_store_op` 对模板数据生效。我们的应用不会使用模板缓冲,所以加载和存储的结果并不重要。 67 | 68 | ```rust,noplaypen 69 | .initial_layout(vk::ImageLayout::UNDEFINED) 70 | .final_layout(vk::ImageLayout::PRESENT_SRC_KHR); 71 | ``` 72 | 73 | 在 Vulkan 中,纹理和帧缓冲是以具有特定像素格式的 `vk::Image` 对象来表示的。不过你可以根据你在对图像做的事情改变内存中像素的布局。 74 | 75 | 一些常见的布局包括: 76 | 77 | * `vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL` – 图像会被用作颜色附件 78 | * `vk::ImageLayout::PRESENT_SRC_KHR` – 图像被用在交换链中进行呈现操作 79 | * `vk::ImageLayout::TRANSFER_DST_OPTIMAL` – 图像用作赋值操作的目标 80 | 81 | 我们会在纹理章节中对这一主题进行更深入的探讨,不过现在我们只要知道图像需要先被转换到特定的布局,以便进行下一步的操作。 82 | 83 | `initial_layout` 指定图像在渲染流程开始前所具有的布局,而 `final_layout` 指定图像在渲染流程结束后将会自动转换到的布局。我们将 `initial_layout` 设置为 `vk::ImageLaout::UNDEFINED`,表明我们不关心图像输入时的布局。这也意味着图像中的内容不一定会被保留,但没关系,反正我们也打算清空它了。而在渲染之后,我们希望图像可以被在交换链上呈现,所以我们将 `final_layout` 设置为 `vk::ImageLayout::PRESENT_SRC_KHR`。 84 | 85 | ## 子流程与附件引用 86 | 87 | 一个渲染流程可以由多个子流程组成。子流程是一系列的渲染操作,每个渲染操作都依赖于之前的子流程处理后帧缓冲中的内容。例如,许多后处理(post-processing)效果就是前面的处理结果上叠加一系列操作来实现的。如果你将这些渲染操作组合成一个渲染流程,Vulkan 就可以对这些操作进行重新排序,以便更好地利用内存带宽来提高性能。不过在我们的第一个三角形程序中,我们只需要一个子流程。 88 | 89 | 每个子流程都要引用一个或多个附件,这些附件通过 `vk::AttachmentReference` 结构体指定: 90 | 91 | ```rust,noplaypen 92 | let color_attachment_ref = vk::AttachmentReference::builder() 93 | .attachment(0) 94 | .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); 95 | ``` 96 | 97 | `attachment` 参数通过附件描述符数组的索引指定要引用哪个附件。我们的附件描述数组中只有一个 `vk::AttachmentDescription`,所以我们将索引置为 `0`。`layout` 用于指定子流程开始时附件的布局,Vulkan 会在子流程开始时自动将附件转换到这个布局。我们打算将附件用作颜色缓冲,所以 `vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL` 布局会给我们最好的性能,正如它的名字所说的那样。 98 | 99 | 子流程使用 `vk::SubpassDescription` 结构体来描述: 100 | 101 | ```rust,noplaypen 102 | let color_attachments = &[color_attachment_ref]; 103 | let subpass = vk::SubpassDescription::builder() 104 | .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) 105 | .color_attachments(color_attachments); 106 | ``` 107 | 108 | Vulkan 在将来也可能支持计算子流程,所以我们需要明确指定这是一个图形子流程。然后我们指定对颜色附件的引用。 109 | 110 | 这里设置的颜色附着将会被片元着色器使用,对应我们在片元着色器中的 `layout(location = 0) out vec4 outColor` 指令。 111 | 112 | 以下类型的附件也可以被子流程使用: 113 | 114 | * `input_attachments` – 可以被着色器读取的附件 115 | * `resolve_attachments` – 用于多重采样颜色附件的附件 116 | * `depth_stencil_attachment` – 用于深度和模板数据的附件 117 | * `preserve_attachments` – 子流程不会使用的附件,但其中的数据必须被保留 118 | 119 | ## 渲染流程 120 | 121 | 现在,我们已经设置好了附件和与之关联的子流程,我们可以开始创建渲染流程了。在 `AppData` 中 `pipeline_layout` 字段的上面添加一个成员变量来存储 `vk::RenderPass` 对象: 122 | 123 | ```rust,noplaypen 124 | struct AppData { 125 | // ... 126 | render_pass: vk::RenderPass, 127 | pipeline_layout: vk::PipelineLayout, 128 | } 129 | ``` 130 | 131 | 接着,我们就可以用附件和子流程数组填充 `vk::RenderPassCreateInfo` 结构体,来创建渲染渲染对象了: 132 | 133 | ```rust,noplaypen 134 | let attachments = &[color_attachment]; 135 | let subpasses = &[subpass]; 136 | let info = vk::RenderPassCreateInfo::builder() 137 | .attachments(attachments) 138 | .subpasses(subpasses); 139 | 140 | data.render_pass = device.create_render_pass(&info, None)?; 141 | ``` 142 | 143 | 和管线布局一样,渲染流程会在整个程序中被使用,所以我们只在 `App::destroy` 中清理它: 144 | 145 | Just like the pipeline layout, the render pass will be referenced throughout the program, so it should only be cleaned up at the end in `App::destroy`: 146 | 147 | ```rust,noplaypen 148 | unsafe fn destroy(&mut self) { 149 | self.device.destroy_pipeline_layout(self.data.pipeline_layout, None); 150 | self.device.destroy_render_pass(self.data.render_pass, None); 151 | // ... 152 | } 153 | ``` 154 | 155 | 到此为止我们已经做了很多工作,在下一章中,我们会将这些工作整合起来,最终创建出图形管线对象! 156 | -------------------------------------------------------------------------------- /book/src/presentation/image_views.md: -------------------------------------------------------------------------------- 1 | # 图像视图 (Image views) 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/07_image_views.rs) 8 | 9 | 要在渲染管线中使用任何 `vk::Image` —— 包括交换链中的那些,我们都需要为其创建一个图像视图对象 `vk::ImageView`。图像视图就像它的名字所描述的那样,它描述了如何访问图像,以及访问图像的哪一部分。例如,图像视图可以用来表示“一张图像应该被视为一张没有多级渐远层级(mipmapping levels)的二维纹理”。 10 | 11 | 在本章中,我们会实现一个 `create_swapchain_image_views` 函数,来为交换链中的每张图像创建一个基本的图像视图,这样我们就可以在之后的章节中将它们用作渲染目标。 12 | 13 | 首先,在 `AppData` 结构体中添加一个字段,用来存储图像视图: 14 | 15 | ```rust,noplaypen 16 | struct AppData { 17 | // ... 18 | swapchain_image_views: Vec, 19 | } 20 | 21 | ``` 22 | 23 | 创建一个 `create_swapchain_image_views` 函数,并在 `App::create` 中创建完交换链之后调用它: 24 | 25 | ```rust,noplaypen 26 | impl App { 27 | unsafe fn create(window: &Window) -> Result { 28 | // ... 29 | create_swapchain(window, &instance, &device, &mut data)?; 30 | create_swapchain_image_views(&device, &mut data)?; 31 | // ... 32 | } 33 | } 34 | 35 | unsafe fn create_swapchain_image_views( 36 | device: &Device, 37 | data: &mut AppData, 38 | ) -> Result<()> { 39 | Ok(()) 40 | } 41 | ``` 42 | 43 | 接着,我们实现 `create_swapchain_image_views` 函数,遍历交换链图像,并为每一张图像创建图像视图: 44 | 45 | ```rust,noplaypen 46 | unsafe fn create_swapchain_image_views( 47 | device: &Device, 48 | data: &mut AppData, 49 | ) -> Result<()> { 50 | data.swapchain_image_views = data 51 | .swapchain_images 52 | .iter() 53 | .map(|i| { 54 | 55 | }) 56 | .collect::, _>>()?; 57 | 58 | Ok(()) 59 | } 60 | ``` 61 | 62 | 对于我们要创建的每一个图像视图,我们首先定义它的颜色分量映射。这允许你对颜色通道进行重新排序。例如,你可以将所有通道映射到红色通道,从而创建一个单色纹理。你也可以将常量值 `0` 和 `1` 映射到通道上。在我们的例子中,我们将使用默认的映射: 63 | 64 | ```rust,noplaypen 65 | let components = vk::ComponentMapping::builder() 66 | .r(vk::ComponentSwizzle::IDENTITY) 67 | .g(vk::ComponentSwizzle::IDENTITY) 68 | .b(vk::ComponentSwizzle::IDENTITY) 69 | .a(vk::ComponentSwizzle::IDENTITY); 70 | ``` 71 | 72 | 73 | 接着,我们为图像视图定义子资源(subresource)范围,它描述了图像的用途以及应该访问图像的哪一部分。这里,我们的图像将被用作没有多级渐远层级,也没有多个层次的颜色目标: 74 | 75 | ```rust,noplaypen 76 | let subresource_range = vk::ImageSubresourceRange::builder() 77 | .aspect_mask(vk::ImageAspectFlags::COLOR) 78 | .base_mip_level(0) 79 | .level_count(1) 80 | .base_array_layer(0) 81 | .layer_count(1); 82 | ``` 83 | 84 | 如果你在编写一个立体 3D 应用,那么你可以创建一个包含多个层次的交换链图像视图。然后你可以访问不同的层次,并分别为左眼和右眼的视角创建各自的图像视图。 85 | 86 | 现在,我们创建一个 `vk::ImageViewCreateInfo` 结构体来提供创建图像视图所需的参数: 87 | 88 | ```rust,noplaypen 89 | let info = vk::ImageViewCreateInfo::builder() 90 | .image(*i) 91 | .view_type(vk::ImageViewType::_2D) 92 | .format(data.swapchain_format) 93 | .components(components) 94 | .subresource_range(subresource_range); 95 | ``` 96 | 97 | `view_type` 和 `format` 字段指定图像数据应该如何被解释。`view_type` 字段用于指定图像应该被视为一维纹理、二维纹理、三维纹理还是立方体贴图。 98 | 99 | 接下来就只要调用 `create_image_view` 函数了: 100 | 101 | ```rust,noplaypen 102 | device.create_image_view(&info, None) 103 | ``` 104 | 105 | 不同于交换链中的图像,图像视图是由我们显式创建的,所以我们需要在 `App::destroy` 中添加一个类似的循环来销毁它们: 106 | 107 | ```rust,noplaypen 108 | unsafe fn destroy(&mut self) { 109 | self.data.swapchain_image_views 110 | .iter() 111 | .for_each(|v| self.device.destroy_image_view(*v, None)); 112 | // ... 113 | } 114 | ``` 115 | 116 | 图像视图已经足以让我们把图像作为纹理使用了,但它还不能用作渲染目标。这还需要一个额外的间接步骤 —— *帧缓冲*(framebuffer)。但在这之前我们需要先建立图形管线。 117 | -------------------------------------------------------------------------------- /book/src/presentation/window_surface.md: -------------------------------------------------------------------------------- 1 | # 窗口表面 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/05_window_surface.rs) 8 | 9 | Vulkan 是一个平台无关的 API,因此它不能直接与窗口系统进行交互。要在屏幕上呈现结果,我们需要使用一系列 WSI(Window System Interface,窗口系统接口)扩展来建立 Vulkan 与窗口系统之间的连接。在本章中,我们将讨论第一个扩展,即 `VK_KHR_surface`。它暴露了一个 `vk::SurfaceKHR` 类型,表示一种用于呈现图像的抽象表面。我们程序中的窗口表面将由我们用 `winit` 打开的窗口来支持。 10 | 11 | `VK_KHR_surface` 扩展是一个实例级扩展,我们实际上已经启用了它,因为它被包含在 `vk_window::get_required_instance_extensions` 返回的列表中。该列表还包含了我们将在接下来的几章中使用的其他 WSI 扩展。 12 | 13 | 窗口表面需要在创建实例之后立即创建,因为窗口表面实际上会影响物理设备的选择。我们之所以现在才讲窗口表面的创建,是因为窗口表面是“渲染目标与呈现”这一更大主题的一部分,在“基本设置”那部分里解释窗口表面会引起混乱。此外还要注意,窗口表面是 Vulkan 里一个完全可选的组件,如果你只需要离屏渲染,Vulkan 也可以在不创建窗口的情况下进行渲染(而 OpenGL 就必须用创建一个不可见窗口这种投机取巧的方式)。 14 | 15 | 16 | 要使用 `VK_KHR_surface` 扩展,我们除了要导入 `vk::SurfaceKHR` 类型,还需要导入 `vulkanalia` 的扩展 trait `vk::KhrSurfaceExtension`: 17 | 18 | ```rust,noplaypen 19 | use vulkanalia::vk::SurfaceKHR 20 | use vulkanalia::vk::KhrSurfaceExtension; 21 | ``` 22 | 23 | ## 创建窗口表面 24 | 25 | 首先,在 `AppData` 中,在**其他字段的上面**添加一个 `surface` 字段: 26 | 27 | ```rust,noplaypen 28 | struct AppData { 29 | surface: vk::SurfaceKHR, 30 | // ... 31 | } 32 | ``` 33 | 34 | 虽然 `vk::SurfaceKHR` 对象及其用法是与平台无关的,但创建它的过程不是,创建 `vk::SurfaceKHR` 的具体过程依赖于窗口系统的细节。例如在 Windows 上,创建 `vk::SurfaceKHR` 需要 `HWND` 和 `HMODULE` 句柄。因此,扩展中有一个特定于平台的附加部分,例如在 Windows 上,它是 `VK_KHR_win32_surface`。平台特定的附加部分也会被自动包含在 `vk_window::get_required_instance_extensions` 的列表中。 35 | 36 | 我将演示如何在 Windows 上使用这个特定于平台的扩展来创建表面,但实际上在本教程中我们不会使用它。`vulkanalia` 已经提供了 `vk_window::create_surface`,它可以处理平台之间的差异。不过在我们开始使用它之前,了解幕后的工作原理是很有好处的。 37 | 38 | 因为窗口表面是一个 Vulkan 对象,所以和其他 Vulkan 对象一样,创建它需要填充一个的 `vk::Win32SurfaceCreateInfoKHR` 结构体。它有两个重要的参数:`hinstance` 和 `hwnd`,分别是进程和窗口的句柄。 39 | 40 | ```rust,noplaypen 41 | use winit::platform::windows::WindowExtWindows; 42 | 43 | let info = vk::Win32SurfaceCreateInfoKHR::builder() 44 | .hinstance(window.hinstance()) 45 | .hwnd(window.hwnd()); 46 | ``` 47 | 48 | `WindowExtWindows` 特性是从 `winit` 中导入的,它允许我们在 `winit` 的 `Window` 结构体上访问平台特定的方法。在这种情况下,它允许我们获取由 `winit` 创建的窗口所在进程的句柄(`hinstance`)和窗口的句柄(`hwnd`)。 49 | 50 | 之后使用 `create_win32_surface_khr` 创建表面,该函数包括用于表面创建的详细信息和自定义分配器的参数。从技术上讲,这是一个 WSI 扩展函数,但它的使用频率很高,所以标准的 Vulkan 加载器也会加载它,因而它不需要像其他扩展一样显式加载。不过我们还是需要为扩展 `VK_KHR_win32_surface` 导入 `vulkanalia` 的扩展 trait `vk::KhrWin32SurfaceExtension`。 51 | 52 | ```rust,noplaypen 53 | use vk::KhrWin32SurfaceExtension; 54 | 55 | let surface = instance.create_win32_surface_khr(&info, None).unwrap(); 56 | ``` 57 | 58 | 在其他平台(如 Linux)上创建表面的过程也和上面类似。例如在 Linux 上需要使用 `create_xcb_surface_khr` 函数,该函数接受 XCB 连接和窗口,并在背后调用 X11 的 API。 59 | 60 | `vk_window::create_surface` 函数在不同的平台上使用不同的实现执行完全相同的操作。现在,我们将其集成到程序中。在 `App::create` 中,在选择物理设备之前,调用该函数: 61 | 62 | ```rust,noplaypen 63 | unsafe fn create(window: &Window) -> Result { 64 | // ... 65 | let instance = create_instance(window, &entry, &mut data)?; 66 | data.surface = vk_window::create_surface(&instance, &window, &window)?; 67 | pick_physical_device(&instance, &mut data)?; 68 | // ... 69 | } 70 | ``` 71 | 72 | 参数是 Vulkan 实例和 `winit` 窗口。一旦我们创建了表面,我们就需要在 `App::destroy` 中使用 Vulkan API 销毁它: 73 | 74 | ```rust,noplaypen 75 | unsafe fn destroy(&mut self) { 76 | // ... 77 | self.instance.destroy_surface_khr(self.data.surface, None); 78 | self.instance.destroy_instance(None); 79 | } 80 | ``` 81 | 82 | 确保在销毁实例之前销毁表面。 83 | 84 | ## 查询呈现(presentation)支持 85 | 86 | 尽管 Vulkan 的实现可能支持窗口系统集成,但这并不意味着系统中的每个设备都支持。因此,我们需要扩展 `pick_physical_device` 函数的功能,以确保我们选择的设备能够向我们创建的表面呈现图像。因为呈现是与队列相关的功能,所以我们实际上是要找到一个支持向我们创建的表面进行呈现的队列族。 87 | 88 | 事实上,支持绘制指令的队列族和支持呈现的队列族可能并不重叠。因此,我们必须考虑呈现队列不同于图形队列的可能性,并修改 `QueueFamilyIndices` 结构体来解决此问题: 89 | 90 | ```rust,noplaypen 91 | struct QueueFamilyIndices { 92 | graphics: u32, 93 | present: u32, 94 | } 95 | ``` 96 | 97 | 接下来,我们将修改 `QueueFamilyIndices::get` 方法,以查找能向我们的窗口表面进行呈现的队列族。该方法使用 `get_physical_device_surface_support_khr` 函数,它以物理设备、队列族索引和表面为参数,并返回这个物理设备、队列族和表面的组合是否支持呈现: 98 | 99 | ```rust,noplaypen 100 | let mut present = None; 101 | for (index, properties) in properties.iter().enumerate() { 102 | if instance.get_physical_device_surface_support_khr( 103 | physical_device, 104 | index as u32, 105 | data.surface, 106 | )? { 107 | present = Some(index as u32); 108 | break; 109 | } 110 | } 111 | ``` 112 | 113 | 我们还需要将 `present` 添加到最终的表达式中: 114 | 115 | ```rust,noplaypen 116 | if let (Some(graphics), Some(present)) = (graphics, present) { 117 | Ok(Self { graphics, present }) 118 | } else { 119 | Err(anyhow!(SuitabilityError("Missing required queue families."))) 120 | } 121 | ``` 122 | 123 | 请注意,这两个索引最终很可能指涉到相同的队列族,但在整个程序中,我们将把它们视为独立的队列,这样我们就可以用统一的方式来处理它们。你也可以添加逻辑来优先选择能在同一个队列中进行绘制和呈现的物理设备,以提高性能。 124 | 125 | ## 创建呈现队列 126 | 127 | 最后一件事是修改逻辑设备的创建过程,以创建呈现队列并取得其 `vk::Queue` 句柄。在 `AppData` 中添加一个字段来保存呈现队列的句柄: 128 | 129 | ```rust,noplaypen 130 | struct AppData { 131 | // ... 132 | present_queue: vk::Queue, 133 | } 134 | ``` 135 | 136 | 接下来,我们需要创建多个 `vk::DeviceQueueCreateInfo` 结构体来从两个队列族中创建队列。一种简单的方法是创建一个集合,用来去重并保存所有需要的队列族。我们将在 `create_logical_device` 函数中完成这个操作: 137 | 138 | ```rust,noplaypen 139 | let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?; 140 | 141 | let mut unique_indices = HashSet::new(); 142 | unique_indices.insert(indices.graphics); 143 | unique_indices.insert(indices.present); 144 | 145 | let queue_priorities = &[1.0]; 146 | let queue_infos = unique_indices 147 | .iter() 148 | .map(|i| { 149 | vk::DeviceQueueCreateInfo::builder() 150 | .queue_family_index(*i) 151 | .queue_priorities(queue_priorities) 152 | }) 153 | .collect::>(); 154 | ``` 155 | 156 | 然后删除之前的 `queue_infos` 切片,并为 `vk::DeviceCreateInfo` 提供一个 `queue_infos` 列表的引用: 157 | 158 | ```rust,noplaypen 159 | let info = vk::DeviceCreateInfo::builder() 160 | .queue_create_infos(&queue_infos) 161 | .enabled_layer_names(&layers) 162 | .enabled_extension_names(&extensions) 163 | .enabled_features(&features); 164 | ``` 165 | 166 | 最后,添加一个调用来获取队列句柄: 167 | 168 | ```rust,noplaypen 169 | data.present_queue = device.get_device_queue(indices.present, 0); 170 | ``` 171 | 172 | 如果队列族相同,那么现在这两个句柄很可能具有相同的值。在下一章中,我们将讨论交换链以及它们如何使我们能够向表面呈现图像。 173 | -------------------------------------------------------------------------------- /book/src/setup/base_code.md: -------------------------------------------------------------------------------- 1 | # 基础代码 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/00_base_code.rs) 8 | 9 | 在开发环境一章中,我们创建了一个 Cargo 项目添加了必要的依赖项目。在本章中,我们会用下面的代码替换 `src/main.rs` 中的内容: 10 | 11 | ```rust 12 | #![allow( 13 | dead_code, 14 | unused_variables, 15 | clippy::too_many_arguments, 16 | clippy::unnecessary_wraps 17 | )] 18 | 19 | use anyhow::Result; 20 | use winit::dpi::LogicalSize; 21 | use winit::event::{Event, WindowEvent}; 22 | use winit::event_loop::, EventLoop; 23 | use winit::window::{Window, WindowBuilder}; 24 | 25 | fn main() -> Result<()> { 26 | pretty_env_logger::init(); 27 | 28 | // 创建窗口 29 | 30 | let event_loop = EventLoop::new()?; 31 | let window = WindowBuilder::new() 32 | .with_title("Vulkan Tutorial (Rust)") 33 | .with_inner_size(LogicalSize::new(1024, 768)) 34 | .build(&event_loop)?; 35 | 36 | // 初始化应用程序 37 | 38 | let mut app = unsafe { App::create(&window)? }; 39 | let mut app = unsafe { App::create(&window)? }; 40 | event_loop.run(move |event, elwt| { 41 | match event { 42 | // Request a redraw when all events were processed. 43 | Event::AboutToWait => window.request_redraw(), 44 | Event::WindowEvent { event, .. } => match event { 45 | // Render a frame if our Vulkan app is not being destroyed. 46 | WindowEvent::RedrawRequested if !elwt.exiting() => unsafe { app.render(&window) }.unwrap(), 47 | // Destroy our Vulkan app. 48 | WindowEvent::CloseRequested => { 49 | elwt.exit(); 50 | unsafe { app.destroy(); } 51 | } 52 | _ => {} 53 | } 54 | _ => {} 55 | } 56 | })?; 57 | 58 | Ok(()) 59 | } 60 | 61 | /// 我们的 Vulkan 应用程序 62 | #[derive(Clone, Debug)] 63 | struct App {} 64 | 65 | impl App { 66 | /// 创建 Vulkan App 67 | unsafe fn create(window: &Window) -> Result { 68 | Ok(Self {}) 69 | } 70 | 71 | /// 渲染帧 72 | unsafe fn render(&mut self, window: &Window) -> Result<()> { 73 | Ok(()) 74 | } 75 | 76 | /// 销毁 Vulkan App 77 | unsafe fn destroy(&mut self) {} 78 | } 79 | 80 | /// 我们的 Vulkan 应用程序所使用的 Vulkan 句柄和相关属性 81 | #[derive(Clone, Debug, Default)] 82 | struct AppData {} 83 | ``` 84 | 85 | 首先我们导入 `anyhow::Result`,这样我们就可以为程序中所有可失败的函数使用 `anyhow` 提供的 [`Result`](https://docs.rs/anyhow/latest/anyhow/type.Result.html) 类型。接下来我们导入所有 `winit` 类型,这些类型将被用于创建窗口并且启动窗口的事件循环。 86 | 87 | 接着是我们的 `main` 函数(它返回一个 `anyhow::Result`)。这个函数首先初始化 `pretty_env_logger`,它将会把日志打印到控制台(稍后会展示)。 88 | 89 | 接着,我们创建一个事件循环(event loop),并用 `winit` 和 `LogicalSize` 来创建一个窗口作为渲染的目标。`LogicalSize` 会根据你的显示器的 DPI 来缩放窗口。如果你想了解更多关于 UI 缩放的内容,可以阅读 [`winit` 文档](https://docs.rs/winit/latest/winit/dpi/index.html)。 90 | 91 | 接着,我们创建一个我们的 Vulkan 应用(`App`)的实例,并进入渲染循环。这个循环会持续将我们的场景渲染到窗口,直到你请求关闭窗口 —— 此时应用程序会被销毁并且程序会退出。`destroying` 标志是必要的,因为在应用程序被销毁时,我们不希望继续渲染场景,否则程序很可能会在尝试访问已被销毁的 Vulkan 资源时崩溃。 92 | 93 | 最后是 `App` 和 `AppData`,`App` 会被用来实现 Vulkan 程序所需的设置、渲染和析构逻辑。在接下来的章节中,我们都会围绕这个 Vulkan 程序工作。我们会创建非常多的 Vulkan 资源,`AppData` 会被用作容纳这些资源的容器,这样我们就可以轻松地将这些资源传递给函数。 94 | 95 | 这样做非常方便,因为我们接下来的很多章节都是加入一个接受 `&mut AppData` 的函数,创建并初始化 Vulkan 资源。这些函数会在 `App::create` 构造器中被调用来初始化我们的 Vulkan 应用。并且,在程序结束前,这些 Vulkan 资源会被 `App::destory` 方法销毁。 96 | 97 | ## 一个关于安全性的注解 98 | 99 | 所有的 Vulkan 函数,无论是原始函数还是它们的封装,在 `vulkanalia` 中都是被标记为 `unsafe` 的。这是因为许多 Vulkan 函数对如何调用它们作了限制,而 Rust 无法确保这些限制(除非引入一个更高阶的接口来隐藏 Vulkan API,例如 [`vulkano`](https://vulkano.rs))。 100 | 101 | 本教程通过把所有调用 Vulkan 函数的函数和方法标记为 `unsafe` 来解决这一问题。这可以最大程度上减少语法噪音,但一个真实的程序中你可能希望自己封装一个安全的接口,并自行确保调用 Vulkan 函数时的正确性。 102 | 103 | ## 资源管理 104 | 105 | 正如在 C 中使用 `malloc` 分配的每一块内存都需要对应一个 `free` 调用一样,对于我们创建的每一个 Vulkan 对象,我们都要在不再需要它时显式地将其销毁。在 Rust 中,使用[资源分配即初始化(Resource Acuisition Is Initialization, RAII)](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)配合像 `Rc` 和 `Arc` 这样的智能指针来自动管理资源是可行的。然而, 的作者选择在教程中显式地创建和销毁 Vulkan 对象,并且我也打算采用同样的方式。毕竟,Vulkan 的特点就是每一个操作都要显式地进行以避免错误,所以显式地管理对象的生命周期也是很好的学习 API 工作方式的途径。 106 | 107 | 在完成本教程之后,你就可以编写 Rust 结构体来包装 Vulkan 对象,并在其 `Drop` 实现中释放 Vulkan 对象,从而实现自动资源管理。对于大规模的 Vulkan 程序而言,RAII 是推荐的模式,但是为了学习,了解背后发生了什么也是很有好处的。 108 | 109 | Vulkan 对象要么是由 `create_xxx` 之类的函数直接创建出来的,要么是通过另一个对象和 `allocate_xxx` 之类的函数分配出来的。在确保一个对象不会再使用之后,你需要用对应的 `destroy_xxx` 或者 `free_xxx` 来销毁它。这些函数的参数通常因对象的类型不同而有所不同,但是它们都有一个共同的参数:`allocator`。这是一个可选参数,允许你指定一个自定义内存分配器的回调函数。在本教程中我们会忽略这个参数,总是传递 `None`。 110 | -------------------------------------------------------------------------------- /book/src/setup/instance.md: -------------------------------------------------------------------------------- 1 | # Vulkan 实例 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/01_instance_creation.rs) 8 | 9 | 你首先要做的事情就是通过创建一个*实例*来初始化 Vulkan 库。实例是你的应用程序和 Vulkan 库之间的连接,创建它涉及到向驱动程序指定一些关于你的应用程序的细节。首先,添加下面的导入: 10 | 11 | ```rust,noplaypen 12 | use anyhow::{anyhow, Result}; 13 | use log::*; 14 | use vulkanalia::loader::{LibloadingLoader, LIBRARY}; 15 | use vulkanalia::window as vk_window; 16 | use vulkanalia::prelude::v1_0::*; 17 | ``` 18 | 19 | 这里我们引入 [`anyhow!`](https://docs.rs/anyhow/latest/anyhow/macro.anyhow.html) 宏,这个宏可以让我们轻松地构造 `anyhow` 错误的实例。然后,我们引入 `log::*`,这样我们就可以使用 `log` crate 中的日志宏。接下来,我们引入 `LibloadingLoader`,它是 `vulkanalia` 提供的 `libloading` 集成,我们会用它来从 Vulkan 共享库中加载最初的 Vulkan 函数。你操作系统上的 Vulkan 共享库(例如 Windows 上的 `vulkan-1.dll`)将会被导入为 `LIBRARY`。 20 | 21 | 接着我们将 `vulkanalia` 对窗口的集成导入为 `vk_window`,我们将在本章中使用它来枚举渲染到窗口所需的全局 Vulkan 扩展。在将来的章节中,我们还将使用 `vk_window` 来将 Vulkan 实例与 `winit` 窗口链接起来。 22 | 23 | 最后我们从 `vulkanalia` 引入 Vulkan 1.0 的 `prelude` 模块,它将为本章和将来的章节提供我们需要的所有其他 Vulkan 相关的导入。 24 | 25 | 要创建一个实例,我们就要用我们的应用程序的一些信息来填充一个 `vk::ApplicationInfo` 结构体。技术上来说,这些数据是可选的,但它们可以给驱动程序提供一些有用的信息,以便优化我们的特定应用程序(例如我们的应用程序使用了某个众所周知的图形引擎,这个引擎具有某些特定的行为)。我们将在函数 `create_instance` 中创建 `vk::ApplicationInfo` 结构体,`create_instance` 函数接受我们的窗口和一个 Vulkan 入口点(entry point,我们将在后面创建)并返回一个 Vulkan 实例: 26 | 27 | ```rust,noplaypen 28 | unsafe fn create_instance(window: &Window, entry: &Entry) -> Result { 29 | let application_info = vk::ApplicationInfo::builder() 30 | .application_name(b"Vulkan Tutorial\0") 31 | .application_version(vk::make_version(1, 0, 0)) 32 | .engine_name(b"No Engine\0") 33 | .engine_version(vk::make_version(1, 0, 0)) 34 | .api_version(vk::make_version(1, 0, 0)); 35 | } 36 | ``` 37 | 38 | 在 Vulkan 中,许多信息都是通过结构体而非函数参数传递的,所以我们再需要填充一个结构体,来提供创建一个实例所需的信息。下一个结构体不是可选的,它会告诉 Vulkan 驱动程序我们想要使用哪些全局扩展和校验层。这里的“全局”意味着这些扩展和校验层适用于整个程序,而不是特定的设备。“全局”和“设备”的概念将在接下来的几章中逐渐变得清晰。首先我们需要使用 `vulkanalia` 的窗口集成 `vk_window` 来枚举所需的全局扩展,并将它们转换为以空字符结尾的 C 字符串(null-terminated C strings,`*const c_char`): 39 | 40 | ```rust,noplaypen 41 | let extensions = vk_window::get_required_instance_extensions(window) 42 | .iter() 43 | .map(|e| e.as_ptr()) 44 | .collect::>(); 45 | ``` 46 | 47 | 在有了所需的全局扩展列表之后,我们就可以使用传入此函数的 Vulkan 入口点来创建 Vulkan 实例并将其返回了: 48 | 49 | ```rust,noplaypen 50 | let info = vk::InstanceCreateInfo::builder() 51 | .application_info(&application_info) 52 | .enabled_extension_names(&extensions); 53 | 54 | Ok(entry.create_instance(&info, None)?) 55 | ``` 56 | 57 | 如你所见,Vulkan 中的对象创建函数参数的一般模式是: 58 | 59 | * 包含创建信息的结构体的引用 60 | * 可选的自定义分配器回调的引用,本教程中始终为 `None` 61 | 62 | 现在我们有了一个可以通过入口点创建 Vulkan 实例的函数,接下来我们需要创建一个 Vulkan 入口点。这个入口点将加载用于查询实例支持和创建实例的 Vulkan 函数。但在此之前,先向我们的 `App` 结构体添加一些字段来存储我们将要创建的 Vulkan 入口点和实例: 63 | 64 | ```rust,noplaypen 65 | struct App { 66 | entry: Entry, 67 | instance: Instance, 68 | } 69 | ``` 70 | 71 | 接着,像这样更新 `App::create` 方法,以填充 `App` 中的这些字段: 72 | 73 | ```rust,noplaypen 74 | unsafe fn create(window: &Window) -> Result { 75 | let loader = LibloadingLoader::new(LIBRARY)?; 76 | let entry = Entry::new(loader).map_err(|b| anyhow!("{}", b))?; 77 | let instance = create_instance(window, &entry)?; 78 | Ok(Self { entry, instance }) 79 | } 80 | ``` 81 | 82 | 这里我们首先创建了一个 Vulkan 函数加载器,用来从 Vulkan 共享库中加载最初的 Vulkan 函数,接着我们使用这个函数加载器创建 Vulkan 入口点,这个入口点将会加载我们需要的所有 Vulkan 函数。最后,我们用 Vulkan 入口点调用 `create_instance` 函数来创建 Vulkan 实例。 83 | 84 | ## 清理工作 85 | 86 | 只有当程序将要退出时,`Instance` 实例才应该被销毁。可以在 `App::destroy` 方法中使用 `destroy_instance` 销毁实例: 87 | 88 | ```rust,noplaypen 89 | unsafe fn destroy(&mut self) { 90 | self.instance.destroy_instance(None); 91 | } 92 | ``` 93 | 94 | 和创建对象所用的 Vulkan 函数一样,用于销毁对象的 Vulkan 函数也接受一个可选的、指向自定义分配器回调的引用。所以和之前一样,我们传入 `None` 来使用默认的分配器行为。 95 | 96 | ## 不合规的 Vulkan 实现 97 | 98 | 不幸的是,并非每个平台都有一个完全符合 Vulkan 规范的 Vulkan API 的实现。在这样的平台上,可能会有一些标准的 Vulkan 特性是不可用的,或者 Vulkan 应用程序的实际行为与 Vulkan 规范有很大的不同。 99 | 100 | 在 Vulkan SDK 的 1.3.216 版本之后,使用不合规 Vulkan 实现的应用程序必须启用一些额外的 Vulkan 扩展。这些兼容性扩展的主要目的是强制开发人员承认他们的应用程序正在使用不合规的 Vulkan 实现,并且他们不期望一切都按 Vulkan 规范进行。 101 | 102 | 本教程会使用这些兼容性 Vulkan 扩展,这样你的程序就可以在缺少完全符合 Vulkan 实现的平台上运行了。 103 | 104 | 然而,你可能会问:“为什么我们要这么做?我们真的需要在一个入门级的 Vulkan 教程中考虑对小众平台的支持吗?”而事实证明,不那么小众的 macOS 就是那些缺少完全符合 Vulkan 实现的平台之一。 105 | 106 | 就如我们在介绍中提到的,Apple 有他们自己的底层图形 API,[Metal](https://en.wikipedia.org/wiki/Metal_(API))。Vulkan SDK 为 macOS 提供的 Vulkan 实现([MoltenVK](https://moltengl.com/))是一个位于应用程序和 Metal 之间的中间层,它将应用程序所做的 Vulkan API 调用转换为 Metal API 调用。因为 MoltenVK [不完全符合 Vulkan 规范](https://www.lunarg.com/wp-content/uploads/2022/05/The-State-of-Vulkan-on-Apple-15APR2022.pdf),所以你需要启用我们在本教程中将要讨论的兼容性 Vulkan 扩展来支持 macOS。 107 | 108 | 顺带一提,尽管 MoltenVK 不是完全合规的实现,但在 macOS 上实践本教程时,应该也是不会有任何问题的。 109 | 110 | ## 启用兼容性扩展 111 | 112 | > **注意:** 就算你用的不是 macOS,本节中添加的一些代码也会在本教程的后续部分中被引用,所以你不能跳过它们! 113 | 114 | 我们希望检查我们所用的 Vulkan 版本是否高于引入兼容性扩展要求的 Vulkan 版本。我们首先添加一个额外的导入: 115 | 116 | ```rust,noplaypen 117 | use vulkanalia::Version; 118 | ``` 119 | 120 | 导入 `vulkanalia::Version` 之后,我们就可以定义一个常量来表示最低版本: 121 | 122 | ```rust,noplaypen 123 | const PORTABILITY_MACOS_VERSION: Version = Version::new(1, 3, 216); 124 | ``` 125 | 126 | 接着,像这样修改枚举扩展并创建实例的代码: 127 | 128 | ```rust,noplaypen 129 | let mut extensions = vk_window::get_required_instance_extensions(window) 130 | .iter() 131 | .map(|e| e.as_ptr()) 132 | .collect::>(); 133 | 134 | // 从 Vulkan 1.3.216 之后,macOS 上的 Vulkan 实现需要启用额外的扩展 135 | let flags = if 136 | cfg!(target_os = "macos") && 137 | entry.version()? >= PORTABILITY_MACOS_VERSION 138 | { 139 | info!("Enabling extensions for macOS portability."); 140 | extensions.push(vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr()); 141 | extensions.push(vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr()); 142 | vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR 143 | } else { 144 | vk::InstanceCreateFlags::empty() 145 | }; 146 | 147 | let info = vk::InstanceCreateInfo::builder() 148 | .application_info(&application_info) 149 | .enabled_extension_names(&extensions) 150 | .flags(flags); 151 | ``` 152 | 153 | 这些代码会在 Vulkan 版本高于我们定义的最小版本,而平台又缺乏完全合规的 Vulkan 实现(这里只检查了 macOS)的情况下启用 `KHR_PORTABILITY_ENUMERATION_EXTENSION ` 兼容性扩展。 154 | 155 | 这段代码还会启用 `KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION` 扩展。启用 `KHR_PORTABILITY_SUBSET_EXTENSION` 需要先启用这个扩展。我们在后面的教程中创建逻辑设备时会用到 `KHR_PORTABILITY_SUBSET_EXTENSION` 扩展。 156 | 157 | ## `Instance` 和 `vk::Instance` 158 | 159 | 当我们调用 `create_instance` 函数时,我们得到的不是 Vulkan 函数 `vkCreateInstance` 返回的原始 Vulkan 实例,而是一个 `vulkanalia` 中的自定义类型,它将原始 Vulkan 实例和为该特定实例加载的函数结合在一起。 160 | 161 | 我们使用的 `Instance` 类型(从 `vulkanalia` 的 `prelude` 模块中导入)不应和 `vk::Instance` 混淆。`vk::Instance` 类型是原始的 Vulkan 实例。在后面的章节中,我们也会用到 `Device` 类型。和 `Instance` 类似的是,`Device` 也由原始 Vulkan 设备(`vk::Device`)和为该设备加载的函数组成。幸运的是,本教程中我们不需要直接使用 `vk::Instance` 或者 `vk::Device`,所以你不用担心弄混它们。 162 | 163 | 因为 `Instance` 中包含了 Vulkan 实例和与之关联的函数,所以 `Instance` 的函数封装也能够在需要原始 Vulkan 实例时提供它。 164 | 165 | 如果你查看 `vkDestroyInstance` 函数的文档,你会发现它接受两个参数:要销毁的实例和可选的自定义分配器回调。然而,`destroy_instance` 只接受可选的自定义分配器回调,因为它能够提供原始 Vulkan 实例作为第一个参数,就像上面描述的那样。 166 | 167 | 创建完实例之后,在继续进行更复杂的步骤之前,是时候拿出校验层,看看我们的调试功能了。 168 | -------------------------------------------------------------------------------- /book/src/setup/logical_device_and_queues.md: -------------------------------------------------------------------------------- 1 | # 逻辑设备与队列 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/04_logical_device.rs) 8 | 9 | 选择了要使用的物理设备之后,我们需要创建一个与之交互的逻辑设备。创建逻辑设备的过程与创建实例的过程相似,即描述我们希望使用的功能。既然我们已经查询了可用的队列族,我们还需要指定要创建哪些队列。如果你有不同的需求,甚至可以从同一个物理设备创建多个逻辑设备。 10 | 11 | 首先,在 `App` 中添加一个新的字段来存储逻辑设备: 12 | 13 | ```rust,noplaypen 14 | struct App { 15 | // ... 16 | device: Device, 17 | } 18 | ``` 19 | 20 | 接下来,在 `App::create` 中调用 `create_logical_device` 函数,并将得到的逻辑设备添加到 `App` 的构造器中: 21 | 22 | ```rust,noplaypen 23 | impl App { 24 | unsafe fn create(window: &Window) -> Result { 25 | // ... 26 | let device = create_logical_device(&entry, &instance, &mut data)?; 27 | Ok(Self { entry, instance, data, device }) 28 | } 29 | } 30 | 31 | unsafe fn create_logical_device( 32 | entry: &Entry, 33 | instance: &Instance, 34 | data: &mut AppData, 35 | ) -> Result { 36 | } 37 | ``` 38 | 39 | ## 指定要创建的队列 40 | 41 | 创建逻辑设备需要再创建一堆结构体来指定一堆细节,首先是 `vk::DeviceQueueCreateInfo`。这个结构体描述了我们单个队列族需要的队列数量。现在,我们只对具备图形功能的队列感兴趣。 42 | 43 | ```rust,noplaypen 44 | let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?; 45 | 46 | let queue_priorities = &[1.0]; 47 | let queue_info = vk::DeviceQueueCreateInfo::builder() 48 | .queue_family_index(indices.graphics) 49 | .queue_priorities(queue_priorities); 50 | ``` 51 | 52 | 当前可用的驱动程序只允许你为每个队列族创建少量队列,实际上你也确实不需要多个队列。因为你可以在多个线程上创建指令缓冲,然后在主线程上一次性提交它们,这样只需要一次低开销的调用。 53 | 54 | Vulkan 允许你为队列分配优先级,使用介于 `0.0` 和 `1.0` 之间的浮点数来影响指令缓冲执行的调度。即使只创建一个队列,也需要指定优先级。 55 | 56 | ## 指定要启用的层 57 | 58 | 接下来要提供的信息与 `vk::InstanceCreateInfo` 结构体相似。同样地,我们需要指定要启用的任何校验层或扩展,但这次指定的扩展是设备特定的,而不是全局的。 59 | 60 | 一个设备特定扩展的例子是 `VK_KHR_swapchain`,它允许你将该设备渲染的图像呈现到窗口中。一些 Vulkan 设备,例如仅支持计算操作的设备,可能不具备此功能。我们将在交换链章节中再次提到这个扩展。 61 | 62 | Vulkan 以前的实现区分了实例和设备特定的校验层,但现在不再是这样了。这意味着在最新的实现中,我们传递给 `enabled_layer_names` 的层名将被忽略。不过,为了与旧版本兼容,还是应该设置这些名称。 63 | 64 | 我们还不会启用任何设备特定的扩展。因此,如果启用了校验,我们将构建一个包含校验层的层名列表。 65 | 66 | ```rust,noplaypen 67 | let layers = if VALIDATION_ENABLED { 68 | vec![VALIDATION_LAYER.as_ptr()] 69 | } else { 70 | vec![] 71 | }; 72 | ``` 73 | 74 | ## 指定要启用的扩展 75 | 76 | 正如 `实例` 一章中所讨论的,对于使用不完全符合 Vulkan 规范的 Vulkan 实现的应用程序,必须启用某些 Vulkan 扩展。在本章中,我们启用了与这些不符合规范的实现兼容所需的实例扩展。在这里,我们将启用出于同样目的所需的设备扩展。 77 | 78 | ```rust,noplaypen 79 | let mut extensions = vec![]; 80 | 81 | // Required by Vulkan SDK on macOS since 1.3.216. 82 | if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION { 83 | extensions.push(vk::KHR_PORTABILITY_SUBSET_EXTENSION.name.as_ptr()); 84 | } 85 | ``` 86 | 87 | ## 指定使用的设备功能 88 | 89 | 下一个需要指定的信息是我们将要使用的设备特性。这些特性是我们在上一章中通过 `get_physical_device_features` 查询到的,比如几何着色器。现在我们不需要任何特殊的东西,所以我们可以简单地定义它,并将所有东西都保留为默认值(`false`)。一旦我们要开始使用 Vulkan 做更有趣的事情,我们会再回到这个结构。 90 | 91 | ```rust,noplaypen 92 | let features = vk::PhysicalDeviceFeatures::builder(); 93 | ``` 94 | 95 | ## 创建逻辑设备 96 | 97 | 有了前面两个结构体、启用的校验层(如果启用)以及设备扩展,我们可以填充最主要的 `vk::DeviceCreateInfo` 结构体。 98 | 99 | ```rust,noplaypen 100 | let queue_infos = &[queue_info]; 101 | let info = vk::DeviceCreateInfo::builder() 102 | .queue_create_infos(queue_infos) 103 | .enabled_layer_names(&layers) 104 | .enabled_extension_names(&extensions) 105 | .enabled_features(&features); 106 | ``` 107 | 108 | 就是这样,我们现在可以调用名为 `create_device` 的方法来实例化逻辑设备了。 109 | 110 | ```rust,noplaypen 111 | let device = instance.create_device(data.physical_device, &info, None)?; 112 | ``` 113 | 114 | 参数是要与逻辑设备交互的物理设备、我们刚刚指定的队列和使用信息,以及可选的分配回调。与实例创建函数类似,如果启用不存在的扩展或指定了不支持的功能,则此调用可能会返回错误。 115 | 116 | 设备应在 `App::destroy` 中被销毁: 117 | 118 | ```rust,noplaypen 119 | unsafe fn destroy(&mut self) { 120 | self.device.destroy_device(None); 121 | // ... 122 | } 123 | ``` 124 | 125 | 逻辑设备不直接与实例交互,因此不作为参数。 126 | 127 | ## 检索队列句柄 128 | 129 | 队列会随着逻辑设备的创建而自动创建,但我们还没有取得与它们交互所用的句柄。首先,在 `AppData` 中添加一个新的字段来存储图形队列的句柄: 130 | 131 | ```rust,noplaypen 132 | struct AppData { 133 | // ... 134 | graphics_queue: vk::Queue, 135 | } 136 | ``` 137 | 138 | 设备队列会在设备销毁时自动清理,所以我们不需要在 `App::destroy` 中做任何处理。 139 | 140 | 我们可以使用 `get_device_queue` 函数来检索每个队列族的队列句柄。参数是逻辑设备、队列族和队列序号。因为我们只从该族中创建一个队列,所以我们只需使用序号 `0`。 141 | 142 | ```rust,noplaypen 143 | data.graphics_queue = device.get_device_queue(indices.graphics, 0); 144 | ``` 145 | 146 | 最后,在 `create_logical_device` 中返回创建的逻辑设备: 147 | 148 | ```rust,noplaypen 149 | Ok(device) 150 | ``` 151 | 152 | 有了逻辑设备和队列句柄,我们现在就可以真正开始使用显卡来执行任务了!在接下来的几章中,我们将设置资源以将结果呈现给窗口系统。 153 | -------------------------------------------------------------------------------- /book/src/setup/physical_devices_and_queue_families.md: -------------------------------------------------------------------------------- 1 | # 物理设备与队列族 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/03_physical_device_selection.rs) 8 | 9 | 在通过 `Instance` 初始化 Vulkan 库之后,我们需要在系统中选择一个支持我们所需功能的图形处理器。事实上,我们可以选择任意多个图形处理器,并同时使用它们,不过在本教程中我们只会选择第一个满足我们需求的图形处理器。 10 | 11 | 我们会添加一个 `pick_physical_device` 函数,用来枚举并选择图形处理器,然后将图形处理器及其相关信息存储在 `AppData` 中。这个函数及其调用的函数会使用一个自定义的错误类型(`SuitabilityError`)来表示物理设备不满足应用程序的需求。这个错误类型会使用 `thiserror` crate 来自动实现错误类型需要的所有的样板代码。 12 | 13 | ```rust,noplaypen 14 | use thiserror::Error; 15 | 16 | impl App { 17 | unsafe fn create(window: &Window) -> Result { 18 | // ... 19 | pick_physical_device(&instance, &mut data)?; 20 | Ok(Self { entry, instance, data }) 21 | } 22 | } 23 | 24 | #[derive(Debug, Error)] 25 | #[error("Missing {0}.")] 26 | pub struct SuitabilityError(pub &'static str); 27 | 28 | unsafe fn pick_physical_device(instance: &Instance, data: &mut AppData) -> Result<()> { 29 | Ok(()) 30 | } 31 | ``` 32 | 33 | 被选中的物理设备会被存储在我们刚添加到 `AppData` 结构体的 `vk::PhysicalDevice` 句柄中。当 `Instance` 被销毁时,这个对象也会被隐式销毁,所以我们不需要在 `App::destroy` 方法中做任何额外的工作。 34 | 35 | ```rust,noplaypen 36 | struct AppData { 37 | // ... 38 | physical_device: vk::PhysicalDevice, 39 | } 40 | ``` 41 | 42 | ## 设备的适用性 43 | 44 | 我们需要一种方法来确定一个物理设备是否符合我们的需求。我们会创建一个用来检测设备适用性的函数,如果我们传给这个函数的物理设备不能完全支持我们需要的功能,那么这个函数会返回一个 `SuitabilityError` 错误: 45 | 46 | ```rust,noplaypen 47 | unsafe fn check_physical_device( 48 | instance: &Instance, 49 | data: &AppData, 50 | physical_device: vk::PhysicalDevice, 51 | ) -> Result<()> { 52 | Ok(()) 53 | } 54 | ``` 55 | 56 | 要评估一个物理设备是否满足我们的需求,我们需要从设备中查询一些详细信息。设备的名称、类型和支持的 Vulkan 版本等基本信息可以使用 `get_physical_device_properties` 查询: 57 | 58 | ```rust,noplaypen 59 | let properties = instance 60 | .get_physical_device_properties(physical_device); 61 | ``` 62 | 63 | 设备对可选特性,例如纹理压缩、64 位浮点类型和多视口渲染(在 VR 中很有用)的支持则可以使用 `get_physical_device_features` 查询: 64 | 65 | ```rust,noplaypen 66 | let features = instance 67 | .get_physical_device_features(physical_device); 68 | ``` 69 | 70 | 我们会在讨论设备内存和队列族(见下一节)的时候再讨论更多可以查询的设备细节。 71 | 72 | 举个例子,假设我们的应用程序只能在支持几何着色器(geometry shader)的独立显卡上运行。那么 `check_physical_device` 函数可能如下所示: 73 | 74 | ```rust,noplaypen 75 | unsafe fn check_physical_device( 76 | instance: &Instance, 77 | data: &AppData, 78 | physical_device: vk::PhysicalDevice, 79 | ) -> Result<()> { 80 | let properties = instance.get_physical_device_properties(physical_device); 81 | if properties.device_type != vk::PhysicalDeviceType::DISCRETE_GPU { 82 | return Err(anyhow!(SuitabilityError("Only discrete GPUs are supported."))); 83 | } 84 | 85 | let features = instance.get_physical_device_features(physical_device); 86 | if features.geometry_shader != vk::TRUE { 87 | return Err(anyhow!(SuitabilityError("Missing geometry shader support."))); 88 | } 89 | 90 | Ok(()) 91 | } 92 | ``` 93 | 94 | 相比于直接选择第一个合适的设备,你也可以给每个设备评分,然后选择得分最高的那个。这样你就可以通过给独立显卡一个更高的分数来优先选择独立显卡,但是如果只有集成显卡可用,就回退到集成显卡。你也可以直接显示设备的名称,然后让用户自行选择。 95 | 96 | 接下来,我们会讨论第一个我们真正需要的功能。 97 | 98 | ## 队列族 99 | 100 | 之前已经介绍过,在 Vulkan 中进行任何操作(从绘制到纹理上传)基本都要将指令提交到队列。不同的队列族能够产生不同种类的队列,而每个队列族都只支持一部分指令。例如,一个队列族可能只允许处理计算指令,或者只允许处理内存传输相关的指令。 101 | 102 | 我们需要查询设备支持的队列族,并且找到一个支持我们所需指令的队列族。为此,我们添加一个新的结构体 `QueueFamilyIndices` 来存储我们需要的队列族的索引。 103 | 104 | 105 | 现在,我们只要找到一个支持图形指令的队列族就好了,那么 `QueueFamilyIndices` 结构体和它的 `impl` 块看起来就像这样: 106 | 107 | ```rust,noplaypen 108 | #[derive(Copy, Clone, Debug)] 109 | struct QueueFamilyIndices { 110 | graphics: u32, 111 | } 112 | 113 | impl QueueFamilyIndices { 114 | unsafe fn get( 115 | instance: &Instance, 116 | data: &AppData, 117 | physical_device: vk::PhysicalDevice, 118 | ) -> Result { 119 | let properties = instance 120 | .get_physical_device_queue_family_properties(physical_device); 121 | 122 | let graphics = properties 123 | .iter() 124 | .position(|p| p.queue_flags.contains(vk::QueueFlags::GRAPHICS)) 125 | .map(|i| i as u32); 126 | 127 | if let Some(graphics) = graphics { 128 | Ok(Self { graphics }) 129 | } else { 130 | Err(anyhow!(SuitabilityError("Missing required queue families."))) 131 | } 132 | } 133 | } 134 | ``` 135 | 136 | `get_physical_device_queue_familiy_properties` 返回的队列属性包含了许多关于物理设备支持的队列族的细节,包括队列族支持的操作类型,以及基于这个队列族能创建多少队列。这里我们要找到第一个支持图形操作的队列族,这个队列族的标志是 `vk::QueueFlags::GRAPHICS`。 137 | 138 | 有了这个酷毙了的队列族查询方法,我们就可以在 `check_physical_device` 函数中使用它,来检查物理设备是否能够处理我们想要使用的指令: 139 | 140 | ```rust,noplaypen 141 | unsafe fn check_physical_device( 142 | instance: &Instance, 143 | data: &AppData, 144 | physical_device: vk::PhysicalDevice, 145 | ) -> Result<()> { 146 | QueueFamilyIndices::get(instance, data, physical_device)?; 147 | Ok(()) 148 | } 149 | ``` 150 | 151 | 152 | 最后,我们遍历所有物理设备,并选中第一个通过 `check_physical_device` 函数检测、符合我们要求的设备。我们更新 `pick_physical_device` 函数: 153 | 154 | ```rust,noplaypen 155 | unsafe fn pick_physical_device(instance: &Instance, data: &mut AppData) -> Result<()> { 156 | for physical_device in instance.enumerate_physical_devices()? { 157 | let properties = instance.get_physical_device_properties(physical_device); 158 | 159 | if let Err(error) = check_physical_device(instance, data, physical_device) { 160 | warn!("Skipping physical device (`{}`): {}", properties.device_name, error); 161 | } else { 162 | info!("Selected physical device (`{}`).", properties.device_name); 163 | data.physical_device = physical_device; 164 | return Ok(()); 165 | } 166 | } 167 | 168 | Err(anyhow!("Failed to find suitable physical device.")) 169 | } 170 | ``` 171 | 172 | 好极了,这就是我们找到正确的物理设备所需要的一切!下一步是创建一个逻辑设备来与之交互。 173 | -------------------------------------------------------------------------------- /book/src/swapchain/recreation.md: -------------------------------------------------------------------------------- 1 | # 重建交换链 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/16_swapchain_recreation.rs) 8 | 9 | 现在应用程序能画出三角形了,但它还没有正确处理一些情况。窗口表面可能会发生变化,使得交换链不再与之兼容。导致这种情况发生的原因之一是窗口的大小发生了变化。我们必须捕获这些事件并重新创建交换链。 10 | 11 | ## 重建交换链 12 | 13 | 添加一个 `App::recreate_swapchain` 方法,它调用 `create_swapchain` 来创建交换链,并调用所有依赖于交换链或者窗口大小的对象的创建函数: 14 | 15 | ```rust,noplaypen 16 | unsafe fn recreate_swapchain(&mut self, window: &Window) -> Result<()> { 17 | self.device.device_wait_idle()?; 18 | create_swapchain(window, &self.instance, &self.device, &mut self.data)?; 19 | create_swapchain_image_views(&self.device, &mut self.data)?; 20 | create_render_pass(&self.instance, &self.device, &mut self.data)?; 21 | create_pipeline(&self.device, &mut self.data)?; 22 | create_framebuffers(&self.device, &mut self.data)?; 23 | create_command_buffers(&self.device, &mut self.data)?; 24 | self.data 25 | .images_in_flight 26 | .resize(self.data.swapchain_images.len(), vk::Fence::null()); 27 | Ok(()) 28 | } 29 | ``` 30 | 31 | 和上一章一样,我们首先调用 `device_wait_idle`,因为我们不能在资源正在被使用时修改它们。显然,我们要做的第一件事就是重建交换链本身。因为图像视图直接基于交换链图像,所以图像视图也需要被重建。因为渲染流程依赖于交换链图像的格式,所以渲染流程也需要被重建。在像窗口大小调整这样的操作中,交换链图像的格式改变的可能性很小,但是我们仍然需要处理这种情况。视口和裁剪矩形的大小在图形管线创建时指定,所以图形管线也需要被重建。使用动态状态来指定视口和裁剪矩形可以避免这种情况。然后,帧缓冲和指令缓冲也直接依赖于交换链图像。最后,我们调整了交换链图像的信号量列表的大小,因为重建后交换链图像的数量可能会发生变化。 32 | 33 | 为了确保在重建这些对象之前清理旧对象,我们应该将清理这些对象的代码从 `App::destroy` 方法中提取出来,移动到一个单独的 `App::destroy_swapchain` 方法中 34 | 35 | ```rust,noplaypen 36 | unsafe fn recreate_swapchain(&mut self, window: &Window) -> Result<()> { 37 | self.device.device_wait_idle()?; 38 | self.destroy_swapchain(); 39 | // ... 40 | } 41 | 42 | unsafe fn destroy_swapchain(&mut self) { 43 | 44 | } 45 | ``` 46 | 47 | 然后我们将所有在交换链刷新时重建的对象的清理代码从 `App::destroy` 移动到 `App::destroy_swapchain` 中: 48 | 49 | ```rust,noplaypen 50 | unsafe fn destroy(&mut self) { 51 | self.destroy_swapchain(); 52 | 53 | self.data.in_flight_fences 54 | .iter() 55 | .for_each(|f| self.device.destroy_fence(*f, None)); 56 | self.data.render_finished_semaphores 57 | .iter() 58 | .for_each(|s| self.device.destroy_semaphore(*s, None)); 59 | self.data.image_available_semaphores 60 | .iter() 61 | .for_each(|s| self.device.destroy_semaphore(*s, None)); 62 | self.device.destroy_command_pool(self.data.command_pool, None); 63 | self.device.destroy_device(None); 64 | self.instance.destroy_surface_khr(self.data.surface, None); 65 | 66 | if VALIDATION_ENABLED { 67 | self.instance.destroy_debug_utils_messenger_ext(self.data.messenger, None); 68 | } 69 | 70 | self.instance.destroy_instance(None); 71 | } 72 | 73 | unsafe fn destroy_swapchain(&mut self) { 74 | self.data.framebuffers 75 | .iter() 76 | .for_each(|f| self.device.destroy_framebuffer(*f, None)); 77 | self.device.free_command_buffers(self.data.command_pool, &self.data.command_buffers); 78 | self.device.destroy_pipeline(self.data.pipeline, None); 79 | self.device.destroy_pipeline_layout(self.data.pipeline_layout, None); 80 | self.device.destroy_render_pass(self.data.render_pass, None); 81 | self.data.swapchain_image_views 82 | .iter() 83 | .for_each(|v| self.device.destroy_image_view(*v, None)); 84 | self.device.destroy_swapchain_khr(self.data.swapchain, None); 85 | } 86 | ``` 87 | 88 | 我们也可以从头开始重建指令池,不过那样就太浪费了。因此我选择使用 `free_command_buffers` 函数清理现有的指令缓冲。这样我们就可以重用现有的指令池来分配新的指令缓冲。 89 | 90 | 这就是重建交换链所需的所有操作!然而,这样做的缺陷就是我们需要在创建新的交换链之前停止所有渲染操作。在旧交换链的图像上的绘制指令仍在执行时创建新的交换链是可能的。你需要将旧交换链传递给 `vk::SwapchainCreateInfoKHR` 结构体中的 `old_swapchain` 字段,并在使用完旧交换链后立即销毁它。 91 | 92 | ## 检测次优或过时的交换链 93 | 94 | 现在我们只需要确定什么时候必须重建交换链,并且调用 `App::recreate_swapchain` 方法。幸运地是,Vulkan 通常会在交换链不再适用时告诉我们。`acquire_next_image_khr` 和 `queue_present_khr` 函数可以返回以下特殊值来指示这一点。 95 | 96 | * `vk::ErrorCode::OUT_OF_DATE_KHR` – 交换链与表面不再兼容,不能再用于渲染。通常发生在窗口大小调整之后。 97 | * `vk::SuccessCode::SUBOPTIMAL_KHR` – 交换链仍然能向表面呈现内容,但是表面的属性不再与交换链完全匹配。 98 | 99 | ```rust,noplaypen 100 | let result = self.device.acquire_next_image_khr( 101 | self.data.swapchain, 102 | u64::MAX, 103 | self.data.image_available_semaphores[self.frame], 104 | vk::Fence::null(), 105 | ); 106 | 107 | let image_index = match result { 108 | Ok((image_index, _)) => image_index as usize, 109 | Err(vk::ErrorCode::OUT_OF_DATE_KHR) => return self.recreate_swapchain(window), 110 | Err(e) => return Err(anyhow!(e)), 111 | }; 112 | ``` 113 | 114 | 如果在尝试从交换链获取图像时发现交换链已经过时,那么就不能再向它呈现内容了。因此我们应该立即重建交换链,并在下一次 `App::render` 调用时再次尝试。 115 | 116 | 你也可以选择在交换链不再最优时重建交换链,但我选择在这种情况下继续进行渲染,因为我们已经获取了一个图像。因为 `vk::SuccessCode::SUBOPTIMAL_KHR` 被认为是一个成功的代码而不是一个错误代码,所以它将被 `match` 块中的 `Ok` 分支处理。 117 | 118 | ```rust,noplaypen 119 | let result = self.device.queue_present_khr(self.data.present_queue, &present_info); 120 | 121 | let changed = result == Ok(vk::SuccessCode::SUBOPTIMAL_KHR) 122 | || result == Err(vk::ErrorCode::OUT_OF_DATE_KHR); 123 | 124 | if changed { 125 | self.recreate_swapchain(window)?; 126 | } else if let Err(e) = result { 127 | return Err(anyhow!(e)); 128 | } 129 | ``` 130 | 131 | `queue_present_khr` 函数返回和 `acquire_next_image_khr` 相同的值,意义也相同。在这种情况下,如果交换链是次优的,我们也会重建交换链,因为我们想要最好的结果。 132 | 133 | ## 显式地处理窗口大小变化 134 | 135 | 尽管许多平台和驱动程序都会在窗口大小改变后自动触发 `vk::ErrorCode::OUT_OF_DATE_KHR`,但并不保证如此。这就是为什么我们要添加一些额外的代码来显式地处理窗口大小变化。首先在 `App` 结构体中添加一个新字段来追踪窗口大小是否发生了改变: 136 | 137 | ```rust,noplaypen 138 | struct App { 139 | // ... 140 | resized: bool, 141 | } 142 | ``` 143 | 144 | 记得在 `App::create` 中将这个新字段初始化为 `false`。然后在 `App::render` 方法中,在调用 `queue_present_khr` 之后也检查这个标志: 145 | 146 | ```rust,noplaypen 147 | let result = self.device.queue_present_khr(self.data.present_queue, &present_info); 148 | 149 | let changed = result == Ok(vk::SuccessCode::SUBOPTIMAL_KHR) 150 | || result == Err(vk::ErrorCode::OUT_OF_DATE_KHR); 151 | 152 | if self.resized || changed { 153 | self.resized = false; 154 | self.recreate_swapchain(window)?; 155 | } else if let Err(e) = result { 156 | return Err(anyhow!(e)); 157 | } 158 | ``` 159 | 160 | 注意要在 `queue_present_khr` 之后执行这个操作,以确保信号量处于一致的状态,否则一个已经发出信号的信号量可能永远不会被正确地等待。现在我们可以在 `main` 中的窗口事件 `match` 块中添加一个分支来实际检测调整大小: 161 | 162 | ```rust,noplaypen 163 | match event { 164 | // ... 165 | Event::WindowEvent { event: WindowEvent::Resized(_), .. } => app.resized = true, 166 | // ... 167 | } 168 | ``` 169 | 170 | 现在尝试运行程序并调整窗口大小,看看帧缓冲是否确实与窗口一起正确调整大小。 171 | 172 | ## 处理窗口最小化 173 | 174 | 还有一种特殊的情况会导致交换链过时,那就是一种特殊的窗口调整大小:窗口最小化。这种情况很特殊,因为它会导致帧缓冲大小为 `0`。在本教程中,我们将通过在窗口最小化时不渲染帧来处理这种情况: 175 | 176 | ```rust,noplaypen 177 | let mut app = unsafe { App::create(&window)? }; 178 | let mut minimized = false; 179 | event_loop.run(move |event,elwt| { 180 | match event { 181 | // ... 182 | Event::WindowEvent { event, .. } => match event { 183 | WindowEvent::RedrawRequested if !elwt.exiting() && !minimized => { 184 | unsafe { app.render(&window) }.unwrap(); 185 | }, 186 | WindowEvent::Resized(size) => { 187 | if size.width == 0 || size.height == 0 { 188 | minimized = true; 189 | } else { 190 | minimized = false; 191 | app.resized = true; 192 | } 193 | } 194 | // ... 195 | } 196 | // ... 197 | } 198 | })?; 199 | ``` 200 | 201 | 恭喜你,你已经完成了你的第一个行为良好的 Vulkan 程序!在下一章中,我们会避免在顶点着色器中硬编码顶点,并实际地使用一个顶点缓冲。 202 | -------------------------------------------------------------------------------- /book/src/texture/combined_image_sampler.md: -------------------------------------------------------------------------------- 1 | # 组合图像采样器 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/25_texture_mapping.rs) | [shader.vert](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/shaders/25/shader.vert) | [shader.frag](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/shaders/25/shader.frag) 8 | 9 | 我们在 uniform 缓冲部分第一次认识了描述符。在本章中我们将看到一种新的描述符:*组合图像采样器*。这种描述符使得着色器可以通过像我们在上一章中创建的采样器对象来访问图像资源。 10 | 11 | 首先我们修改描述符集合布局、描述符池以及描述符集合,使其包含一个组合图像采样器描述符。然后我们将在 `Vertex` 中添加纹理坐标,并修改片元着色器,使其从纹理中读取颜色而不是仅仅插值顶点颜色。 12 | 13 | ## 更新描述符 14 | 15 | 在 `create_descriptor_set_layout` 函数中为组合图像采样器描述符添加一个 `vk::DescriptorSetLayoutBinding`。我们将其放在 uniform 缓冲之后的绑定中: 16 | 17 | ```rust,noplaypen 18 | let sampler_binding = vk::DescriptorSetLayoutBinding::builder() 19 | .binding(1) 20 | .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 21 | .descriptor_count(1) 22 | .stage_flags(vk::ShaderStageFlags::FRAGMENT); 23 | 24 | let bindings = &[ubo_binding, sampler_binding]; 25 | let info = vk::DescriptorSetLayoutCreateInfo::builder() 26 | .bindings(bindings); 27 | ``` 28 | 29 | 记得将 `stage_flags` 设为 `vk::ShaderStageFlags::FRAGMENT`,以指示我们将会在片元着色器中使用组合图像采样器描述符 —— 这是片元的颜色将会被确定的地方。在顶点着色器中使用纹理采样是可能的,例如通过[高度图](https://en.wikipedia.org/wiki/Heightmap)动态地改变顶点网格的形状。 30 | 31 | 我们必须创建一个更大的描述符池,在 `vk::DescriptorPoolCreateInfo` 中添加另一个 `vk::DescriptorType::COMBINED_IMAGE_SAMPLER` 类型的 `vk::DescriptorPoolSize`,从而为组合图像采样器的分配腾出空间。转到 `create_descriptor_pool` 函数,为组合图像采样器描述符添加一个 `vk::DescriptorPoolSize`: 32 | 33 | ```rust,noplaypen 34 | let sampler_size = vk::DescriptorPoolSize::builder() 35 | .type_(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 36 | .descriptor_count(data.swapchain_images.len() as u32); 37 | 38 | let pool_sizes = &[ubo_size, sampler_size]; 39 | let info = vk::DescriptorPoolCreateInfo::builder() 40 | .pool_sizes(pool_sizes) 41 | .max_sets(data.swapchain_images.len() as u32); 42 | ``` 43 | 44 | 有一些问题是校验层无法捕获的,描述符池不够大就是一个很好的例子:从 Vulkan 1.1 开始,如果描述符池不够大,`allocate_descriptor_sets` 可能会失败并返回错误码 `vk::ErrorCode::OUT_OF_POOL_MEMORY`,但驱动程序也可能会尝试在内部解决这个问题。这意味着有时(取决于硬件、池大小和分配大小)驱动程序会允许我们超出描述符池的限制。其他时候,`allocate_descriptor_sets` 将会失败并返回 `vk::ErrorCode::OUT_OF_POOL_MEMORY`。如果分配在某些机器上成功,但在其他机器上失败,这可能会让人特别沮丧。 45 | 46 | 因为 Vulkan 把分配的责任转移给了驱动程序,所以只分配与描述符池创建时相应的 `descriptor_count` 成员指定的某种类型的描述符(`vk::DescriptorType::COMBINED_IMAGE_SAMPLER` 等)不再是一个严格的要求。然而,最好还是这样做。并且在将来,如果你启用了[最佳实践校验](https://vulkan.lunarg.com/doc/view/1.1.126.0/windows/best_practices.html),`VK_LAYER_KHRONOS_validation` 将会对这种类型的问题发出警告。 47 | 48 | 最后一步就是将实际的图像和采样器绑定到描述符集合中的描述符上了。转到 `create_descriptor_sets` 函数。组合图像采样器结构的资源必须在 `vk::DescriptorImageInfo` 结构中指定,就像 uniform 缓冲描述符的缓冲资源在 `vk::DescriptorBufferInfo` 结构中指定一样。这就是前一章中的对象结合在一起的地方。 49 | 50 | ```rust,noplaypen 51 | let info = vk::DescriptorImageInfo::builder() 52 | .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) 53 | .image_view(data.texture_image_view) 54 | .sampler(data.texture_sampler); 55 | 56 | let image_info = &[info]; 57 | let sampler_write = vk::WriteDescriptorSet::builder() 58 | .dst_set(data.descriptor_sets[i]) 59 | .dst_binding(1) 60 | .dst_array_element(0) 61 | .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 62 | .image_info(image_info); 63 | 64 | device.update_descriptor_sets( 65 | &[ubo_write, sampler_write], 66 | &[] as &[vk::CopyDescriptorSet], 67 | ); 68 | ``` 69 | 70 | 描述符需要使用图像信息更新。这一次我们使用 `image_info` 数组,而不是 `buffer_info`。描述符现在已经可以被着色器使用了! 71 | 72 | ## 纹理坐标 73 | 74 | 纹理映射还缺少一个重要的组成部分,那就是每个顶点的实际坐标。这些坐标决定了图像如何映射到几何体上。 75 | 76 | ```rust,noplaypen 77 | #[repr(C)] 78 | #[derive(Copy, Clone, Debug)] 79 | struct Vertex { 80 | pos: Vec2, 81 | color: Vec3, 82 | tex_coord: Vec2, 83 | } 84 | 85 | impl Vertex { 86 | const fn new(pos: Vec2, color: Vec3, tex_coord: Vec2) -> Self { 87 | Self { pos, color, tex_coord } 88 | } 89 | 90 | fn binding_description() -> vk::VertexInputBindingDescription { 91 | vk::VertexInputBindingDescription::builder() 92 | .binding(0) 93 | .stride(size_of::() as u32) 94 | .input_rate(vk::VertexInputRate::VERTEX) 95 | .build() 96 | } 97 | 98 | fn attribute_descriptions() -> [vk::VertexInputAttributeDescription; 3] { 99 | let pos = vk::VertexInputAttributeDescription::builder() 100 | .binding(0) 101 | .location(0) 102 | .format(vk::Format::R32G32_SFLOAT) 103 | .offset(0) 104 | .build(); 105 | let color = vk::VertexInputAttributeDescription::builder() 106 | .binding(0) 107 | .location(1) 108 | .format(vk::Format::R32G32B32_SFLOAT) 109 | .offset(size_of::() as u32) 110 | .build(); 111 | let tex_coord = vk::VertexInputAttributeDescription::builder() 112 | .binding(0) 113 | .location(2) 114 | .format(vk::Format::R32G32_SFLOAT) 115 | .offset((size_of::() + size_of::()) as u32) 116 | .build(); 117 | [pos, color, tex_coord] 118 | } 119 | } 120 | ``` 121 | 122 | 修改 `Vertex` 结构体并为纹理坐标添加一个 `Vec2` 类型的字段。记得也添加一条 `vk::VertexInputAttributeDescription`,这样我们就能在顶点着色器中访问纹理坐标了。这样我们才能将它们从顶点着色器传递给片元着色器,以便在正方形表面上进行插值。 123 | 124 | ```rust,noplaypen 125 | static VERTICES: [Vertex; 4] = [ 126 | Vertex::new(vec2(-0.5, -0.5), vec3(1.0, 0.0, 0.0), vec2(1.0, 0.0)), 127 | Vertex::new(vec2(0.5, -0.5), vec3(0.0, 1.0, 0.0), vec2(0.0, 0.0)), 128 | Vertex::new(vec2(0.5, 0.5), vec3(0.0, 0.0, 1.0), vec2(0.0, 1.0)), 129 | Vertex::new(vec2(-0.5, 0.5), vec3(1.0, 1.0, 1.0), vec2(1.0, 1.0)), 130 | ]; 131 | ``` 132 | 133 | 在本教程中,我会简单地使用左上角为 `0, 0`,右下角为 `1, 1` 的坐标填充正方形。你可以随意尝试不同的坐标,并试着使用小于 `0` 或大于 `1` 的坐标来看看寻址模式的效果! 134 | 135 | ## 着色器 136 | 137 | 最后一步就是修改着色器,使其从纹理中采样出颜色。 我们首先需要修改顶点着色器,将纹理坐标传递给片元着色器: 138 | 139 | ```glsl 140 | layout(location = 0) in vec2 inPosition; 141 | layout(location = 1) in vec3 inColor; 142 | layout(location = 2) in vec2 inTexCoord; 143 | 144 | layout(location = 0) out vec3 fragColor; 145 | layout(location = 1) out vec2 fragTexCoord; 146 | 147 | void main() { 148 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); 149 | fragColor = inColor; 150 | fragTexCoord = inTexCoord; 151 | } 152 | ``` 153 | 154 | 和逐顶点颜色一样,`fragTexCoord` 值也会被光栅化器在正方形区域内平滑插值。我们可以通过让片元着色器将纹理坐标作为颜色输出来可视化这一点: 155 | 156 | ```glsl 157 | #version 450 158 | 159 | layout(location = 0) in vec3 fragColor; 160 | layout(location = 1) in vec2 fragTexCoord; 161 | 162 | layout(location = 0) out vec4 outColor; 163 | 164 | void main() { 165 | outColor = vec4(fragTexCoord, 0.0, 1.0); 166 | } 167 | ``` 168 | 169 | 别忘记重新编译着色器!之后,你应该会看到下面的图像。 170 | 171 | ![](../images/texcoord_visualization.png) 172 | 173 | 绿色通道代表了水平坐标,红色通道代表了垂直坐标。黑色和黄色的角落证实了纹理坐标在正方形上从 `0, 0` 插值到 `1, 1` 的正确性。使用颜色可视化数据是着色器编程中的 `printf` 调试等效方法,因为没有更好的选择! 174 | 175 | 组合图像采样器描述符在 GLSL 中是使用采样器 uniform 表示的。在片元着色器中添加一个引用: 176 | 177 | ```glsl 178 | layout(binding = 1) uniform sampler2D texSampler; 179 | ``` 180 | 181 | 对于其他类型的图像,有等效的 `sampler1D` 和 `sampler3D` 类型。记得在这里使用正确的绑定。 182 | 183 | ```glsl 184 | void main() { 185 | outColor = texture(texSampler, fragTexCoord); 186 | } 187 | ``` 188 | 189 | 使用内建的 `texture` 函数采样纹理。`texture` 函数接受一个 `sampler` 和一个坐标作为参数。采样器会自动处理过滤和变换。当你运行应用程序时,你应该会在正方形上看到纹理: 190 | 191 | ![](../images/texture_on_square.png) 192 | 193 | 试着缩放纹理坐标,得到大于 `1` 的值,来试验寻址模式的功能。例如,当使用 `vk::SamplerAddressMode::REPEAT` 时,下面的片元着色器会产生下面的图像: 194 | 195 | ```glsl 196 | void main() { 197 | outColor = texture(texSampler, fragTexCoord * 2.0); 198 | } 199 | ``` 200 | 201 | ![](../images/texture_on_square_repeated.png) 202 | 203 | 你也可以用顶点颜色来改变纹理颜色: 204 | 205 | ```glsl 206 | void main() { 207 | outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0); 208 | } 209 | ``` 210 | 211 | 我在这里分离了 RGB 和 alpha 通道,以免缩放 alpha 通道。 212 | 213 | ![](../images/texture_on_square_colorized.png) 214 | 215 | 现在你知道如何在着色器中访问图像了!当与帧缓冲中也写入的图像结合使用时,这是一种非常强大的技术。你可以使用这些图像作为输入来实现很酷的效果,比如后处理和 3D 世界中的相机显示。 216 | -------------------------------------------------------------------------------- /book/src/texture/image_view_and_sampler.md: -------------------------------------------------------------------------------- 1 | # 图像视图与采样器 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/24_sampler.rs) 8 | 9 | 在本章中,我们将会创建两个新的图形管线中采样图像所必须的资源。第一个资源我们在之前使用交换链图像时已经见过了,但是第二个资源是全新的,它与着色器如何从图像中读取像素有关。 10 | 11 | ## 纹理图像视图 12 | 13 | 正如我们之前在交换链图像和帧缓冲中所见到的,图像不能直接访问,而是要通过图像视图来访问。我们也需要为纹理图像创建这样的图像视图。 14 | 15 | 在 `AppData` 中添加一个 `vk::ImageView` 字段来保存纹理图像的图像视图,并创建一个新的函数 `create_texture_image_view` 来创建它: 16 | 17 | ```rust,noplaypen 18 | impl App { 19 | unsafe fn create(window: &Window) -> Result { 20 | // ... 21 | create_texture_image(&instance, &device, &mut data)?; 22 | create_texture_image_view(&device, &mut data)?; 23 | // ... 24 | } 25 | } 26 | 27 | struct AppData { 28 | // ... 29 | texture_image: vk::Image, 30 | texture_image_memory: vk::DeviceMemory, 31 | texture_image_view: vk::ImageView, 32 | // ... 33 | } 34 | 35 | unsafe fn create_texture_image_view(device: &Device, data: &mut AppData) -> Result<()> { 36 | Ok(()) 37 | } 38 | ``` 39 | 40 | 这个函数的代码可以直接基于 `create_swapchain_image_views`。你只需要修改 `format` 和 `image`: 41 | 42 | ```rust,noplaypen 43 | let subresource_range = vk::ImageSubresourceRange::builder() 44 | .aspect_mask(vk::ImageAspectFlags::COLOR) 45 | .base_mip_level(0) 46 | .level_count(1) 47 | .base_array_layer(0) 48 | .layer_count(1); 49 | 50 | let info = vk::ImageViewCreateInfo::builder() 51 | .image(data.texture_image) 52 | .view_type(vk::ImageViewType::_2D) 53 | .format(vk::Format::R8G8B8A8_SRGB) 54 | .subresource_range(subresource_range); 55 | ``` 56 | 57 | 我省略了显式的 `components` 初始化,因为 `vk::ComponentSwizzle::IDENTITY` 的值就是 `0`。通过调用 `create_image_view` 来完成图像视图的创建: 58 | 59 | ```rust,noplaypen 60 | data.texture_image_view = device.create_image_view(&info, None)?; 61 | ``` 62 | 63 | 这里的很多逻辑都和 `create_swapchain_image_views` 重复了,你可能希望将这些逻辑抽出来,写成一个新的 `create_image_view` 函数: 64 | 65 | ```rust,noplaypen 66 | unsafe fn create_image_view( 67 | device: &Device, 68 | image: vk::Image, 69 | format: vk::Format, 70 | ) -> Result { 71 | let subresource_range = vk::ImageSubresourceRange::builder() 72 | .aspect_mask(vk::ImageAspectFlags::COLOR) 73 | .base_mip_level(0) 74 | .level_count(1) 75 | .base_array_layer(0) 76 | .layer_count(1); 77 | 78 | let info = vk::ImageViewCreateInfo::builder() 79 | .image(image) 80 | .view_type(vk::ImageViewType::_2D) 81 | .format(format) 82 | .subresource_range(subresource_range); 83 | 84 | Ok(device.create_image_view(&info, None)?) 85 | } 86 | ``` 87 | 88 | 有了这个函数,`create_texture_image_view` 函数就可以简化为: 89 | 90 | ```rust,noplaypen 91 | unsafe fn create_texture_image_view(device: &Device, data: &mut AppData) -> Result<()> { 92 | data.texture_image_view = create_image_view( 93 | device, 94 | data.texture_image, 95 | vk::Format::R8G8B8A8_SRGB, 96 | )?; 97 | 98 | Ok(()) 99 | } 100 | ``` 101 | 102 | 而 `create_swapchain_image_views` 可以简化为: 103 | 104 | ```rust,noplaypen 105 | unsafe fn create_swapchain_image_views(device: &Device, data: &mut AppData) -> Result<()> { 106 | data.swapchain_image_views = data 107 | .swapchain_images 108 | .iter() 109 | .map(|i| create_image_view(device, *i, data.swapchain_format)) 110 | .collect::, _>>()?; 111 | 112 | Ok(()) 113 | } 114 | ``` 115 | 116 | 记得在程序结束时,在销毁图像之前销毁图像视图: 117 | 118 | ```rust,noplaypen 119 | unsafe fn destroy(&mut self) { 120 | self.destroy_swapchain(); 121 | self.device.destroy_image_view(self.data.texture_image_view, None); 122 | // ... 123 | } 124 | ``` 125 | 126 | ## 采样器(sampler) 127 | 128 | 着色器可以直接从图像中读取像素,但当图像被用作纹理时,这种做法并不常见。纹理通常是通过采样器访问的,采样器会应用过滤和变换来计算最终的颜色。 129 | 130 | 这些过滤器有助于解决采样过密(oversampling)的问题。考虑一个纹理,它被映射到的几何体的片段数比纹素数多。如果你只是简单地为每个片段的纹理坐标取最近的纹素,那么你会得到如下第一张图像的结果: 131 | 132 | ![](../images/texture_filtering.png) 133 | 134 | 如果你用线性插值结合了最近的 4 个纹素,那么你会得到一个更平滑的结果,如右图所示。当然,左图可能更适合你的应用程序的艺术风格要求(想想 Minecraft),但是在传统的图形应用程序中,右图更受欢迎。当从纹理中读取颜色时,采样器对象会自动为你应用这种过滤。 135 | 136 | 而当纹素数量多于片段时,就会出现采样过疏(undersampling)的问题。当以锐角采样棋盘纹理这样的高频图案时,就会出现伪影: 137 | 138 | ![](../images/anisotropic_filtering.png) 139 | 140 | 在左图中,纹理在远处变成了一团模糊的东西。解决这个问题的方法是[各向异性过滤](https://en.wikipedia.org/wiki/Anisotropic_filtering),采样器也可以自动应用它。 141 | 142 | 除了这些过滤器之外,采样器还可以处理变换。它决定了当你尝试通过其*寻址模式(addressing mode)*读取图像外的纹素时会发生什么。下面的图像展示了一些可能性: 143 | 144 | ![](../images/texture_addressing.png) 145 | 146 | 现在我们创建一个 `create_texture_sampler` 函数来设置一个这样的采样器对象。之后我们会在着色器中使用这个采样器来从纹理中读取颜色。 147 | 148 | ```rust,noplaypen 149 | impl App { 150 | unsafe fn create(window: &Window) -> Result { 151 | // ... 152 | create_texture_image(&instance, &device, &mut data)?; 153 | create_texture_image_view(&device, &mut data)?; 154 | create_texture_sampler(&device, &mut data)?; 155 | // ... 156 | } 157 | } 158 | 159 | unsafe fn create_texture_sampler(device: &Device, data: &mut AppData) -> Result<()> { 160 | Ok(()) 161 | } 162 | ``` 163 | 164 | 采样器是通过 `vk::SamplerCreateInfo` 结构进行配置的,它指定了它应该应用的所有过滤器和变换。 165 | 166 | ```rust,noplaypen 167 | let info = vk::SamplerCreateInfo::builder() 168 | .mag_filter(vk::Filter::LINEAR) 169 | .min_filter(vk::Filter::LINEAR) 170 | // continued... 171 | ``` 172 | 173 | `mag_filter` 和 `min_filter` 字段指定了如何插值放大或缩小的纹素。放大涉及上面描述的采样过密问题,而缩小涉及到采样过疏。选项有 `vk::Filter::NEAREST` 和 `vk::Filter::LINEAR`,分别对应上面图像中演示的模式。 174 | 175 | ```rust,noplaypen 176 | .mipmap_mode(vk::SamplerMipmapMode::LINEAR) 177 | ``` 178 | 179 | ```rust,noplaypen 180 | .address_mode_u(vk::SamplerAddressMode::REPEAT) 181 | .address_mode_v(vk::SamplerAddressMode::REPEAT) 182 | .address_mode_w(vk::SamplerAddressMode::REPEAT) 183 | ``` 184 | 185 | 寻址模式可以使用 `address_mode` 字段按轴指定。可用的值如下所示。上面的图像演示了其中的大多数。请注意,这些轴被称为 U、V 和 W,而不是 X、Y 和 Z。这是纹理空间坐标的约定。 186 | 187 | * `vk::SamplerAddressMode::REPEAT` – 超出图像尺寸时重复纹理。 188 | * `vk::SamplerAddressMode::MIRRORED_REPEAT` – 类似于重复,但是在超出尺寸时反转坐标以镜像图像。 189 | * `vk::SamplerAddressMode::CLAMP_TO_EDGE` – 超出图像尺寸时取最接近坐标的边缘的颜色。 190 | * `vk::SamplerAddressMode::MIRROR_CLAMP_TO_EDGE` – 类似于 clamp to edge,但是使用与最接近边缘相反的边缘。 191 | * `vk::SamplerAddressMode::CLAMP_TO_BORDER` – 超出图像尺寸时返回一个纯色。 192 | 193 | 这里我们用什么寻址模式都无所谓,因为在本教程中我们不会采样到图像外面。不过,重复模式可能是最常见的模式,因为它可以被用来在绘制像地板和墙壁这样的物体时平铺纹理。 194 | 195 | ```rust,noplaypen 196 | .anisotropy_enable(true) 197 | .max_anisotropy(16.0) 198 | ``` 199 | 200 | 如果要使用各向异性过滤,我们需要指定两个字段。除非遇到了性能问题,不然没理由不启用各向异性过滤。`max_anisotropy` 字段限制了用于计算最终颜色的纹素样本的数量。将其设为较低的值能带来更好的性能,但也会降低渲染的质量。目前没有任何图形硬件会使用超过 16 个样本,因为就算使用了超过这个数量的样本,带来的视觉提升也可以忽略。 201 | 202 | ```rust,noplaypen 203 | .border_color(vk::BorderColor::INT_OPAQUE_BLACK) 204 | ``` 205 | 206 | `border_color` 字段指定了在使用 clamp to border 寻址模式时,采样超出图像范围时返回的颜色。可以返回黑色、白色或透明色,可以是浮点或整数格式。不能指定任意颜色。 207 | 208 | ```rust,noplaypen 209 | .unnormalized_coordinates(false) 210 | ``` 211 | 212 | `unnormalized_coordinates` 字段指定了你是否要使用未标准化的坐标系来寻址图像中的纹素。如果这个字段是 `true`,那么你可以简单地使用 `[0, width)` 和 `[0, height)` 范围内的坐标。如果是 `false`,那么纹素将在所有轴上使用 `[0, 1)` 范围来寻址。现实世界中的应用几乎总是使用标准化坐标,因为这样就可以用完全相同的坐标使用不同分辨率的纹理。 213 | 214 | ```rust,noplaypen 215 | .compare_enable(false) 216 | .compare_op(vk::CompareOp::ALWAYS) 217 | ``` 218 | 219 | 如果启用了比较函数,那么纹素将首先与一个值进行比较,比较的结果将被用于过滤操作。这主要用于阴影贴图上的[近似百分比过滤](https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch11.html)。我们将在后面的章节中介绍这个。 220 | 221 | ```rust,noplaypen 222 | .mipmap_mode(vk::SamplerMipmapMode::LINEAR) 223 | .mip_lod_bias(0.0) 224 | .min_lod(0.0) 225 | .max_lod(0.0); 226 | ``` 227 | 228 | 接下来的这些字段都与多级渐远有关。我们将在[后面的章节](/Generating_Mipmaps)中介绍多级渐远,但简单来说它就是另一种过滤器。 229 | 230 | 采样器的功能现在已经完全定义了。添加一个 `AppData` 字段来保存采样器对象的句柄: 231 | 232 | ```rust,noplaypen 233 | struct AppData { 234 | // ... 235 | texture_image_view: vk::ImageView, 236 | texture_sampler: vk::Sampler, 237 | // ... 238 | } 239 | ``` 240 | 241 | 接着使用 `create_sampler` 函数来创建这个采样器: 242 | 243 | ```rust,noplaypen 244 | data.texture_sampler = device.create_sampler(&info, None)?; 245 | ``` 246 | 247 | 注意采样器不引用 `vk::Image`。采样器是一个独立的对象,它提供了从纹理中提取颜色的接口。采样器可以应用于任何你想要的图像,无论是 1D、2D 还是 3D。这与许多旧的 API 不同,旧的 API 将纹理图像和过滤器组合成一个单一的状态。 248 | 249 | 在程序结束、我们不再访问图像之后销毁采样器: 250 | 251 | ```rust,noplaypen 252 | unsafe fn destroy(&mut self) { 253 | self.destroy_swapchain(); 254 | self.device.destroy_sampler(self.data.texture_sampler, None); 255 | // ... 256 | } 257 | ``` 258 | 259 | ## 各向异性设备特性 260 | 261 | 如果你现在运行程序,你会看到一条类似这样的校验层消息: 262 | 263 | ![](../images/validation_layer_anisotropy.png) 264 | 265 | 这是因为各向异性过滤其实是一项可选的设备特性。我们需要更新 `create_logical_device` 函数来请求它: 266 | 267 | ```rust,noplaypen 268 | let features = vk::PhysicalDeviceFeatures::builder() 269 | .sampler_anisotropy(true); 270 | ``` 271 | 272 | 并且尽管现代显卡不支持各向异性过滤的可能性非常小,但我们还是应该更新 `check_physical_device` 来检查它是否可用: 273 | 274 | ```rust,noplaypen 275 | unsafe fn check_physical_device( 276 | instance: &Instance, 277 | data: &AppData, 278 | physical_device: vk::PhysicalDevice, 279 | ) -> Result<()> { 280 | // ... 281 | 282 | let features = instance.get_physical_device_features(physical_device); 283 | if features.sampler_anisotropy != vk::TRUE { 284 | return Err(anyhow!(SuitabilityError("No sampler anisotropy."))); 285 | } 286 | 287 | Ok(()) 288 | } 289 | ``` 290 | 291 | `get_physical_device_features` 重用了 `vk::PhysicalDeviceFeatures` 结构体,通过设置布尔值来指示支持哪些特性,而不是请求哪些特性。 292 | 293 | 你也可以根据条件来设置,而不是强制使用各向异性过滤: 294 | 295 | ```rust,noplaypen 296 | .anisotropy_enable(false) 297 | .max_anisotropy(1.0) 298 | ``` 299 | 300 | 下一章中,我们将把图像和采样器对象暴露给着色器,以便将纹理绘制到正方形上。 301 | -------------------------------------------------------------------------------- /book/src/uniform/descriptor_pool_and_sets.md: -------------------------------------------------------------------------------- 1 | # 描述符池与描述符集合 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/22_descriptor_sets.rs) 8 | 9 | 上一章提到的描述符集合布局描述了可以绑定的描述符的类型。在本章中,我们将为每个 `vk::Buffer` 资源创建一个描述符集合,以将其绑定到 uniform 缓冲描述符。 10 | 11 | ## 描述符池 12 | 13 | 描述符集合不能被直接创建,而是像指令缓冲一样必须从池中分配。类比指令池之于指令缓冲,描述符集合的等效物不出所料地被称为*描述符池*。我们将编写一个新的函数 `create_descriptor_pool` 来设置它。 14 | 15 | ```rust,noplaypen 16 | impl App { 17 | unsafe fn create(window: &Window) -> Result { 18 | // ... 19 | create_uniform_buffers(&instance, &device, &mut data)?; 20 | create_descriptor_pool(&device, &mut data)?; 21 | // ... 22 | } 23 | } 24 | 25 | unsafe fn create_descriptor_pool(device: &Device, data: &mut AppData) -> Result<()> { 26 | Ok(()) 27 | } 28 | ``` 29 | 30 | 首先我们需要使用 `vk::DescriptorPoolSize` 结构描述我们的描述符集合将包含哪些描述符类型以及它们的数量。 31 | 32 | ```rust,noplaypen 33 | let ubo_size = vk::DescriptorPoolSize::builder() 34 | .type_(vk::DescriptorType::UNIFORM_BUFFER) 35 | .descriptor_count(data.swapchain_images.len() as u32); 36 | ``` 37 | 38 | 我们会为每一帧分配一个这样的描述符。包含了最大描述符集合数量信息的 `vk::DescriptorPoolSize` 结构会被主要的 `vk::DescriptorPoolCreateInfo` 引用: 39 | 40 | ```rust,noplaypen 41 | let pool_sizes = &[ubo_size]; 42 | let info = vk::DescriptorPoolCreateInfo::builder() 43 | .pool_sizes(pool_sizes) 44 | .max_sets(data.swapchain_images.len() as u32); 45 | ``` 46 | 47 | 类似于指令池,这个结构有一个的可选标志 `vk::DescriptorPoolCreateFlags::FREE_DESCRIPTOR_SET`,用于确定是否可以释放单个描述符集合。我们在创建描述符集合后不会再修改它,所以我们不需要这个标志。 48 | 49 | ```rust,noplaypen 50 | struct AppData { 51 | // ... 52 | uniform_buffers: Vec, 53 | uniform_buffers_memory: Vec, 54 | descriptor_pool: vk::DescriptorPool, 55 | // ... 56 | } 57 | ``` 58 | 59 | 在 `AppData` 中添加一个新的字段来存储描述符池的句柄,并调用 `create_descriptor_pool` 来创建它。 60 | 61 | ```rust,noplaypen 62 | data.descriptor_pool = device.create_descriptor_pool(&info, None)?; 63 | ``` 64 | 65 | 当重建交换链时,应该销毁描述符池,因为它取决于图像的数量: 66 | 67 | ```rust,noplaypen 68 | unsafe fn destroy_swapchain(&mut self) { 69 | self.device.destroy_descriptor_pool(self.data.descriptor_pool, None); 70 | // ... 71 | } 72 | ``` 73 | 74 | 并且在 `App::recreate_swapchain` 中重新创建描述符池: 75 | 76 | ```rust,noplaypen 77 | unsafe fn recreate_swapchain(&mut self, window: &Window) -> Result<()> { 78 | // ... 79 | create_uniform_buffers(&self.instance, &self.device, &mut self.data)?; 80 | create_descriptor_pool(&self.device, &mut self.data)?; 81 | // ... 82 | } 83 | ``` 84 | 85 | ## 描述符集合 86 | 87 | 现在我们可以分配描述符集合本身了。添加一个 `create_descriptor_sets` 函数: 88 | 89 | ```rust,noplaypen 90 | impl App { 91 | unsafe fn create(window: &Window) -> Result { 92 | // ... 93 | create_descriptor_pool(&device, &mut data)?; 94 | create_descriptor_sets(&device, &mut data)?; 95 | // ... 96 | } 97 | 98 | unsafe fn recreate_swapchain(&mut self, window: &Window) -> Result<()> { 99 | // .. 100 | create_descriptor_pool(&self.device, &mut self.data)?; 101 | create_descriptor_sets(&self.device, &mut self.data)?; 102 | // .. 103 | } 104 | } 105 | 106 | unsafe fn create_descriptor_sets(device: &Device, data: &mut AppData) -> Result<()> { 107 | Ok(()) 108 | } 109 | ``` 110 | 111 | 描述符集合分配使用 `vk::DescriptorSetAllocateInfo` 结构描述。你需要指定要分配的描述符池,以及描述符集合布局的数组,该数组描述了要分配的每个描述符集合: 112 | 113 | ```rust,noplaypen 114 | let layouts = vec![data.descriptor_set_layout; data.swapchain_images.len()]; 115 | let info = vk::DescriptorSetAllocateInfo::builder() 116 | .descriptor_pool(data.descriptor_pool) 117 | .set_layouts(&layouts); 118 | ``` 119 | 120 | 在我们的例子中,我们将为每个交换链图像创建一个描述符集合,所有的描述符集合都具有相同的布局。不幸的是,我们只能把描述符集合布局复制多次,因为 `set_layouts` 字段需要一个与描述符集合数量相匹配的数组。 121 | 122 | 在 `AppData` 中添加一个字段来保存描述符集合的句柄: 123 | 124 | ```rust,noplaypen 125 | struct AppData { 126 | // ... 127 | descriptor_pool: vk::DescriptorPool, 128 | descriptor_sets: Vec, 129 | // ... 130 | } 131 | ``` 132 | 133 | 并使用 `allocate_descriptor_sets` 分配它们: 134 | 135 | ```rust,noplaypen 136 | data.descriptor_sets = device.allocate_descriptor_sets(&info)?; 137 | ``` 138 | 139 | 你不需要显式地清理描述符集合,因为当描述符池被销毁时,它们将自动释放。调用 `allocate_descriptor_sets` 将分配描述符集合,每个集合都有一个 uniform 缓冲描述符。 140 | 141 | 现在已经分配了描述符集合,但是其中的描述符仍然需要配置。我们现在将添加一个循环来填充每个描述符: 142 | 143 | ```rust,noplaypen 144 | for i in 0..data.swapchain_images.len() { 145 | 146 | } 147 | ``` 148 | 149 | 指向缓冲的描述符 —— 例如我们的 uniform 缓冲描述符 —— 使用 `vk::DescriptorBufferInfo` 结构进行配置。该结构指定了缓冲以及其中包含描述符数据的区域。 150 | 151 | ```rust,noplaypen 152 | for i in 0..data.swapchain_images.len() { 153 | let info = vk::DescriptorBufferInfo::builder() 154 | .buffer(data.uniform_buffers[i]) 155 | .offset(0) 156 | .range(size_of::() as u64); 157 | } 158 | ``` 159 | 160 | 如果你要覆盖整个缓冲,就像我们在这个例子中一样,你也可以使用 `vk::WHOLE_SIZE` 值来表示范围。描述符的配置使用 `update_descriptor_sets` 函数进行更新,该函数以 `vk::WriteDescriptorSet` 结构的数组作为参数。 161 | 162 | ```rust,noplaypen 163 | let buffer_info = &[info]; 164 | let ubo_write = vk::WriteDescriptorSet::builder() 165 | .dst_set(data.descriptor_sets[i]) 166 | .dst_binding(0) 167 | .dst_array_element(0) 168 | // continued... 169 | ``` 170 | 171 | 前两个字段指定了要更新的描述符集合和绑定。我们给 uniform 缓冲绑定索引 `0`。请记住,描述符可以是数组,因此我们还需要用 `dst_array_element` 字段指定要更新的数组中的第一个索引。我们没有使用数组,所以索引是 `0`。 172 | 173 | ```rust,noplaypen 174 | .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) 175 | ``` 176 | 177 | 我们需要再次指定描述符的类型。 178 | 179 | ```rust,noplaypen 180 | .buffer_info(buffer_info); 181 | ``` 182 | 183 | 最后一个字段引用了一个数组,其中包含 `descriptor_count` 个实际配置描述符的结构体。取决于描述符的类型,你需要使用以下三个字段之一:`buffer_info` 字段用于指向缓冲数据的描述符,`image_info` 用于指向图像数据的描述符,`texel_buffer_view` 用于指向缓冲视图的描述符。我们的描述符基于缓冲,所以我们使用 `buffer_info`。 184 | 185 | ```rust,noplaypen 186 | device.update_descriptor_sets(&[ubo_write], &[] as &[vk::CopyDescriptorSet]); 187 | ``` 188 | 189 | 使用 `update_descriptor_sets` 应用更新。它接受两种类型的数组作为参数:`vk::WriteDescriptorSet` 数组和 `vk::CopyDescriptorSet` 数组。后者可以用来将描述符复制到彼此,正如它的名字所暗示的那样。 190 | 191 | ## 使用描述符集合 192 | 193 | 现在我们需要更新 `create_command_buffers` 函数,使用 `cmd_bind_descriptor_sets` 来将每个与交换链图像对应的描述符集合绑定到着色器中的描述符上。这需要在 `cmd_draw_indexed` 调用之前完成: 194 | 195 | ```rust,noplaypen 196 | device.cmd_bind_descriptor_sets( 197 | *command_buffer, 198 | vk::PipelineBindPoint::GRAPHICS, 199 | data.pipeline_layout, 200 | 0, 201 | &[data.descriptor_sets[i]], 202 | &[], 203 | ); 204 | device.cmd_draw_indexed(*command_buffer, INDICES.len() as u32, 1, 0, 0, 0); 205 | ``` 206 | 207 | 不同于顶点和索引缓冲的是,描述符集合并不是专为图形管线而设的。因此我们需要指定我们想要将描述符集合绑定到图形管线还是计算管线。下一个参数是描述符基于的管线布局。接下来的两个参数指定了第一个描述符集合的索引和要绑定的集合数组。我们稍后会回到这个问题。最后一个参数指定了用于动态描述符的偏移量数组。我们将在后面的章节中看到这些。 208 | 209 | 如果你现在运行程序,那么很不幸,你看不到任何东西。问题在于我们在投影矩阵中翻转了 Y 坐标,现在顶点是按逆时针顺序而不是顺时针顺序绘制的。这导致背面剔除生效,并阻止任何几何图形被绘制。进入 `create_pipeline` 函数并修改 `vk::PipelineRasterizationStateCreateInfo` 中的 `front_face` 以纠正这个问题: 210 | 211 | ```rust,noplaypen 212 | .cull_mode(vk::CullModeFlags::BACK) 213 | .front_face(vk::FrontFace::COUNTER_CLOCKWISE) 214 | ``` 215 | 216 | 再次运行程序,你应该能看到以下内容: 217 | 218 | ![](../images/spinning_quad.png) 219 | 220 | 矩形变成了方形,因为投影矩阵现在纠正了宽高比。`App::update_uniform_buffer` 方法负责屏幕调整大小,所以我们不需要在 `App::recreate_swapchain` 中重新创建描述符集合。 221 | 222 | ## 对齐要求 223 | 224 | 到目前为止,我们忽略了一个问题,那就是 Rust 结构中的数据应该如何与着色器中的 uniform 定义匹配。在两者中使用相同的类型似乎是显而易见的: 225 | 226 | ```rust,noplaypen 227 | #[repr(C)] 228 | #[derive(Copy, Clone, Debug)] 229 | struct UniformBufferObject { 230 | model: Mat4, 231 | view: Mat4, 232 | proj: Mat4, 233 | } 234 | ``` 235 | 236 | ```glsl 237 | layout(binding = 0) uniform UniformBufferObject { 238 | mat4 model; 239 | mat4 view; 240 | mat4 proj; 241 | } ubo; 242 | ``` 243 | 244 | 然而这不只是全部。例如,试试看像这样修改结构和着色器: 245 | 246 | ```rust,noplaypen 247 | #[repr(C)] 248 | #[derive(Copy, Clone, Debug)] 249 | struct UniformBufferObject { 250 | foo: Vec2, 251 | model: Mat4, 252 | view: Mat4, 253 | proj: Mat4, 254 | } 255 | ``` 256 | 257 | ```glsl 258 | layout(binding = 0) uniform UniformBufferObject { 259 | vec2 foo; 260 | mat4 model; 261 | mat4 view; 262 | mat4 proj; 263 | } ubo; 264 | ``` 265 | 266 | 重新编译你的着色器和程序并运行它,你会发现五颜六色的正方形消失了!这是因为我们没有考虑到*对齐要求(alignment requirements)*。 267 | 268 | Vulkan 希望你的结构中的数据在内存中以特定的方式对齐,例如: 269 | 270 | * 标量必须以 N (= 4 字节,给定 32 位浮点数) 对齐。 271 | * `vec2` 必须以 2N (= 8 字节) 对齐。 272 | * `vec3` 或 `vec4` 必须以 4N (= 16 字节) 对齐。 273 | * 嵌套结构必须以其成员的基本对齐方式对齐,向上舍入为 16 的倍数。 274 | * `mat4` 矩阵必须与 `vec4` 具有相同的对齐方式。 275 | 276 | 你可以在[规范](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/chap14.html#interfaces-resources-layout)中找到完整的对齐要求列表。 277 | 278 | 我们原先的着色器只有三个 `mat4` 字段,已经满足了对齐要求。由于每个 `mat4` 的大小为 4 x 4 x 4 = 64 字节,`model` 的偏移量为 `0`,`view` 的偏移量为 64,`proj` 的偏移量为 128。所有这些都是 16 的倍数,这就是为什么它碰巧能正常工作。 279 | 280 | 而新的结构则以 `vec2` 开头,而 `vec2` 只有 8 字节大小,因此后面所有的偏移量都会被打乱。现在 `model` 的偏移量为 `8`,`view` 的偏移量为 `72`,`proj` 的偏移量为 `136`,它们都不是 16 的倍数。不幸的是,Rust 对于控制结构体字段的对齐方式没有很好的支持,但是我们可以手动填充来修复对齐问题: 281 | 282 | ```rust,noplaypen 283 | #[repr(C)] 284 | #[derive(Copy, Clone, Debug)] 285 | struct UniformBufferObject { 286 | foo: Vec2, 287 | _padding: [u8; 8], 288 | model: Mat4, 289 | view: Mat4, 290 | proj: Mat4, 291 | } 292 | ``` 293 | 294 | 如果你现在重新编译并再次运行程序,你应该会看到着色器再次正确地接收到矩阵值。 295 | 296 | ## 多个描述符集合 297 | 298 | 正如一些结构和函数调用所暗示的那样,实际上可以同时绑定多个描述符集合。在创建管线布局时,你需要为每个描述符集合指定一个描述符集合布局。然后着色器可以像这样引用特定的描述符集合: 299 | 300 | ```glsl 301 | layout(set = 0, binding = 0) uniform UniformBufferObject { ... } 302 | ``` 303 | 304 | 你可以利用这个特性,将每个对象都不同的描述符和在对象之间共享的描述符分别放入不同的描述符集合中。在这种情况下,你可以避免在绘制调用之间重新绑定大多数描述符,这可能更有效率。 305 | -------------------------------------------------------------------------------- /book/src/vertex/index_buffer.md: -------------------------------------------------------------------------------- 1 | # 索引缓冲 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/20_index_buffer.rs) 8 | 9 | 在真正的应用程序中,你渲染的 3D 网格通常中的许多三角形之间都会共享顶点。即使是像绘制矩形这样简单的事情,也会遇到这种情况: 10 | 11 | ![](../images/vertex_vs_index.svg) 12 | 13 | 渲染一个矩形需要两个三角形,也就是说我们需要一个 6 个顶点的顶点缓冲。问题在于,有两个顶点的数据是重复的,这就导致了 50% 的冗余。对于更复杂的网格而言,平均每个顶点会被 3 个三角形使用,情况只会变得更糟。解决这个问题的方法就是使用*索引缓冲*。 14 | 15 | 一个索引缓冲实质上就是一个指向顶点缓冲的指针构成的数组。它允许你重排顶点数据,并为多个顶点复用现有数据。上面的插图展示了如果我们有一个包含每个独特顶点的顶点缓冲,那么矩形的索引缓冲会是什么样子。前三个索引定义了右上角的三角形,最后三个索引定义了左下角三角形的顶点。 16 | 17 | ## 创建索引缓冲 18 | 19 | 在本章中,我们将修改顶点数据,并添加索引数据来绘制一个像插图中那样的矩形。修改顶点数据以表示四个角: 20 | 21 | ```rust,noplaypen 22 | static VERTICES: [Vertex; 4] = [ 23 | Vertex::new(vec2(-0.5, -0.5), vec3(1.0, 0.0, 0.0)), 24 | Vertex::new(vec2(0.5, -0.5), vec3(0.0, 1.0, 0.0)), 25 | Vertex::new(vec2(0.5, 0.5), vec3(0.0, 0.0, 1.0)), 26 | Vertex::new(vec2(-0.5, 0.5), vec3(1.0, 1.0, 1.0)), 27 | ]; 28 | ``` 29 | 30 | 左上角是红色的,右上角是绿色的,右下角是蓝色的,而左下角是白色的。我们将添加一个新的数组 `INDICES` 来表示索引缓冲的内容。它应该与插图中的索引匹配,以绘制右上角的三角形和左下角的三角形。 31 | 32 | ```rust,noplaypen 33 | const INDICES: &[u16] = &[0, 1, 2, 2, 3, 0]; 34 | ``` 35 | 36 | 取决于 `VERTICES` 中的条目数量,为索引缓冲使用 `u16` 和 `u32` 都是可以的。因为我们使用的顶点数量少于 65,536 个,所以我们可以使用 `u16`。 37 | 38 | 和顶点数据一样,索引也需要被上传到 `vk::Buffer` 中,GPU 才能访问它们。定义两个新的 `AppData` 字段来保存索引缓冲的资源: 39 | 40 | ```rust,noplaypen 41 | struct AppData { 42 | // ... 43 | vertex_buffer: vk::Buffer, 44 | vertex_buffer_memory: vk::DeviceMemory, 45 | index_buffer: vk::Buffer, 46 | index_buffer_memory: vk::DeviceMemory, 47 | } 48 | ``` 49 | 50 | 接下来我们要添加的 `create_index_buffer` 函数和之前的 `create_vertex_buffer` 函数几乎一模一样: 51 | 52 | ```rust,noplaypen 53 | impl App { 54 | unsafe fn create(window: &Window) -> Result { 55 | // ... 56 | create_vertex_buffer(&instance, &device, &mut data)?; 57 | create_index_buffer(&instance, &device, &mut data)?; 58 | // ... 59 | } 60 | } 61 | 62 | unsafe fn create_index_buffer( 63 | instance: &Instance, 64 | device: &Device, 65 | data: &mut AppData, 66 | ) -> Result<()> { 67 | let size = (size_of::() * INDICES.len()) as u64; 68 | 69 | let (staging_buffer, staging_buffer_memory) = create_buffer( 70 | instance, 71 | device, 72 | data, 73 | size, 74 | vk::BufferUsageFlags::TRANSFER_SRC, 75 | vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE, 76 | )?; 77 | 78 | let memory = device.map_memory( 79 | staging_buffer_memory, 80 | 0, 81 | size, 82 | vk::MemoryMapFlags::empty(), 83 | )?; 84 | 85 | memcpy(INDICES.as_ptr(), memory.cast(), INDICES.len()); 86 | 87 | device.unmap_memory(staging_buffer_memory); 88 | 89 | let (index_buffer, index_buffer_memory) = create_buffer( 90 | instance, 91 | device, 92 | data, 93 | size, 94 | vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDEX_BUFFER, 95 | vk::MemoryPropertyFlags::DEVICE_LOCAL, 96 | )?; 97 | 98 | data.index_buffer = index_buffer; 99 | data.index_buffer_memory = index_buffer_memory; 100 | 101 | copy_buffer(device, data, staging_buffer, index_buffer, size)?; 102 | 103 | device.destroy_buffer(staging_buffer, None); 104 | device.free_memory(staging_buffer_memory, None); 105 | 106 | Ok(()) 107 | } 108 | ``` 109 | 110 | 不过还是有两个值得一提的区别,`size` 现在等于索引数量乘以索引类型 —— 即 `u16` 或 `u32` —— 的大小。`index_buffer` 的用途应该是 `vk::BufferUsageFlags::INDEX_BUFFER` 而不是 `vk::BufferUsageFlags::VERTEX_BUFFER`,这是有道理的。除此之外,整个过程完全一样:我们创建一个暂存缓冲,将 `INDICES` 的内容复制到其中,然后将其复制到最终的设备本地索引缓冲。 111 | 112 | 在程序结束时,和顶点缓冲一样,索引缓冲也应该被清理: 113 | 114 | ```rust,noplaypen 115 | unsafe fn destroy(&mut self) { 116 | self.destroy_swapchain(); 117 | self.device.destroy_buffer(self.data.index_buffer, None); 118 | self.device.free_memory(self.data.index_buffer_memory, None); 119 | self.device.destroy_buffer(self.data.vertex_buffer, None); 120 | self.device.free_memory(self.data.vertex_buffer_memory, None); 121 | // ... 122 | } 123 | ``` 124 | 125 | ## 使用索引缓冲 126 | 127 | 在绘制中使用索引缓冲需要修改 `create_command_buffer` 中的两个地方。首先我们需要绑定索引缓冲,就像绑定顶点缓冲时一样。区别在于索引缓冲只能有一个。很不幸,为每个顶点属性使用不同的索引是不可行的,因此即使只有一个属性变化,我们仍然必须完全复制顶点数据。 128 | 129 | ```rust,noplaypen 130 | device.cmd_bind_vertex_buffers(*command_buffer, 0, &[data.vertex_buffer], &[0]); 131 | device.cmd_bind_index_buffer(*command_buffer, data.index_buffer, 0, vk::IndexType::UINT16); 132 | ``` 133 | 134 | 一个索引缓冲通过 `cmd_bind_index_buffer` 来绑定,这个函数接受索引缓冲、字节偏移量和索引数据类型作为参数。如前所述,可能的类型有 `vk::IndexType::UINT16` 和 `vk::IndexType::UINT32`。 135 | 136 | 只绑定索引缓冲还不够,我们要改变绘图指令,以告诉 Vulkan 使用索引缓冲。删除 `cmd_draw` 那一行,并用 `cmd_draw_indexed` 替换: 137 | 138 | ```rust,noplaypen 139 | device.cmd_draw_indexed(*command_buffer, INDICES.len() as u32, 1, 0, 0, 0); 140 | ``` 141 | 142 | `cmd_draw_indexed` 和 `cmd_draw` 的调用方式非常类似。指令缓冲后面的前两个参数指定了索引的数量和实例的数量。我们没有使用实例化,所以只指定 `1` 个实例。索引的数量表示将传递给顶点缓冲的顶点数量。下一个参数指定了索引缓冲的偏移量,传递 `0` 会让显卡从第一个索引开始读取。倒数第二个参数指定了要添加到索引缓冲中的索引的偏移量。最后一个参数指定了实例化(我们没有使用)的偏移量。 143 | 144 | 现在运行程序,然后你应该会看到如下画面: 145 | 146 | ![](../images/indexed_rectangle.png) 147 | 148 | 现在你知道如何使用索引缓冲来重用顶点并节约内存了。这会在我们之后的章节中加载 3D 模型时变得尤为重要。 149 | 150 | 上一章已经提到过,你应该使用一次内存分配来分配多个资源,但事实上你应该更进一步。[驱动开发者建议](https://developer.nvidia.com/vulkan-memory-management)你将多个缓冲,例如顶点缓冲和索引缓冲,存储到一个 `vk::Buffer` 中,并在 `cmd_bind_vertex_buffers` 这样的函数中使用偏移量。这样做的好处是你的数据会因存放得更近而更缓存友好。如果这些资源在相同的渲染操作期间没有被使用,那么甚至可以重用同一块内存 —— 当然前提是数据被更新过。这被称为*别名*(aliasing),并且一些 Vulkan 函数有显式的参数来指定你想要这样做。 151 | -------------------------------------------------------------------------------- /book/src/vertex/staging_buffer.md: -------------------------------------------------------------------------------- 1 | # 暂存缓冲 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/19_staging_buffer.rs) 8 | 9 | 目前我们的顶点缓冲可以正常工作,但是能直接从 CPU 访问的内存对于从显卡读取而言可能并不是最优的。最优内存具有 `vk::MemoryPropertyFlags::DEVICE_LOCAL` 标志,通常位于独立显卡上,无法由 CPU 访问。在本章中,我们将创建两个顶点缓冲。首先是位于 CPU 可访问内存中的*暂存缓冲*,用于将顶点数组中的数据上传至其中;然后是位于设备本地内存中的最终顶点缓冲。接着,我们将使用缓冲复制指令将数据从暂存缓冲复制到实际的顶点缓冲中。 10 | 11 | ## 传输队列 12 | 13 | 缓冲复制指令需要一个支持传输操作的队列族,这种队列族具有 `vk::QueueFlags::TRANSFER` 标志。好消息是,任何具有 `vk::QueueFlags::GRAPHICS` 或 `vk::QueueFlags::COMPUTE` 能力的队列族已经隐式地支持 `vk::QueueFlags::TRANSFER` 操作。在这种情况下,实现不需要在 `queue_flags` 中显式列出这个标志。 14 | 15 | 如果你愿意接受挑战,你仍然可以尝试为传输操作使用不同的队列族。这将需要你对程序进行以下修改: 16 | 17 | - 修改 `QueueFamilyIndices` 和 `QueueFamilyIndices::get`,以明确寻找具有 `vk::QueueFlags::TRANSFER` 标志但不具有 `vk::QueueFlags::GRAPHICS` 的队列族。 18 | - 修改 `create_logical_device`,以请求传输队列的句柄。 19 | - 为在传输队列族上提交的指令缓冲创建第二个指令池。 20 | - 将资源的 `sharing_mode` 改为 `vk::SharingMode::CONCURRENT`,并指定图形队列族和传输队列族。 21 | - 将任何传输指令(在本章中将使用的 `cmd_copy_buffer` 等)提交到传输队列,而不是图形队列。 22 | 23 | 虽然需要付出一些努力,但这将让你深入了解在不同队列族之间共享资源的重要知识。 24 | 25 | ## 抽象化缓冲创建 26 | 27 | 由于我们将在本章中创建多个缓冲,将缓冲创建操作移动到一个辅助函数中是个不错的主意。创建一个名为 `create_buffer` 的新函数,并将 `create_vertex_buffer` 中的代码(除了映射部分)迁移到该函数中: 28 | 29 | ```rust,noplaypen 30 | unsafe fn create_buffer( 31 | instance: &Instance, 32 | device: &Device, 33 | data: &AppData, 34 | size: vk::DeviceSize, 35 | usage: vk::BufferUsageFlags, 36 | properties: vk::MemoryPropertyFlags, 37 | ) -> Result<(vk::Buffer, vk::DeviceMemory)> { 38 | let buffer_info = vk::BufferCreateInfo::builder() 39 | .size(size) 40 | .usage(usage) 41 | .sharing_mode(vk::SharingMode::EXCLUSIVE); 42 | 43 | let buffer = device.create_buffer(&buffer_info, None)?; 44 | 45 | let requirements = device.get_buffer_memory_requirements(buffer); 46 | 47 | let memory_info = vk::MemoryAllocateInfo::builder() 48 | .allocation_size(requirements.size) 49 | .memory_type_index(get_memory_type_index( 50 | instance, 51 | data, 52 | properties, 53 | requirements, 54 | )?); 55 | 56 | let buffer_memory = device.allocate_memory(&memory_info, None)?; 57 | 58 | device.bind_buffer_memory(buffer, buffer_memory, 0)?; 59 | 60 | Ok((buffer, buffer_memory)) 61 | } 62 | ``` 63 | 64 | 确保将缓冲大小、用法以及内存属性添加到函数参数,以便于我们使用此函数创建多种不同类型的缓冲。 65 | 66 | 现在,你可以从 `create_vertex_buffer` 中删除创建缓冲和分配内存的代码,改为调用 `create_buffer`: 67 | 68 | ```rust,noplaypen 69 | unsafe fn create_vertex_buffer( 70 | instance: &Instance, 71 | device: &Device, 72 | data: &mut AppData, 73 | ) -> Result<()> { 74 | let size = (size_of::() * VERTICES.len()) as u64; 75 | 76 | let (vertex_buffer, vertex_buffer_memory) = create_buffer( 77 | instance, 78 | device, 79 | data, 80 | size, 81 | vk::BufferUsageFlags::VERTEX_BUFFER, 82 | vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE, 83 | )?; 84 | 85 | data.vertex_buffer = vertex_buffer; 86 | data.vertex_buffer_memory = vertex_buffer_memory; 87 | 88 | let memory = device.map_memory( 89 | vertex_buffer_memory, 90 | 0, 91 | size, 92 | vk::MemoryMapFlags::empty(), 93 | )?; 94 | 95 | memcpy(VERTICES.as_ptr(), memory.cast(), VERTICES.len()); 96 | 97 | device.unmap_memory(vertex_buffer_memory); 98 | 99 | Ok(()) 100 | } 101 | ``` 102 | 103 | 运行程序,确保顶点缓冲仍然正常工作。 104 | 105 | ## 使用暂存缓冲 106 | 107 | 现在,我们要修改 `create_vertex_buffer`,使其只将主机可见的缓冲作为临时缓冲,并将一个设备本地缓冲用作实际的顶点缓冲。 108 | 109 | ```rust,noplaypen 110 | unsafe fn create_vertex_buffer( 111 | instance: &Instance, 112 | device: &Device, 113 | data: &mut AppData, 114 | ) -> Result<()> { 115 | let size = (size_of::() * VERTICES.len()) as u64; 116 | 117 | let (staging_buffer, staging_buffer_memory) = create_buffer( 118 | instance, 119 | device, 120 | data, 121 | size, 122 | vk::BufferUsageFlags::TRANSFER_SRC, 123 | vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE, 124 | )?; 125 | 126 | let memory = device.map_memory( 127 | staging_buffer_memory, 128 | 0, 129 | size, 130 | vk::MemoryMapFlags::empty(), 131 | )?; 132 | 133 | memcpy(VERTICES.as_ptr(), memory.cast(), VERTICES.len()); 134 | 135 | device.unmap_memory(staging_buffer_memory); 136 | 137 | let (vertex_buffer, vertex_buffer_memory) = create_buffer( 138 | instance, 139 | device, 140 | data, 141 | size, 142 | vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::VERTEX_BUFFER, 143 | vk::MemoryPropertyFlags::DEVICE_LOCAL, 144 | )?; 145 | 146 | data.vertex_buffer = vertex_buffer; 147 | data.vertex_buffer_memory = vertex_buffer_memory; 148 | 149 | Ok(()) 150 | } 151 | ``` 152 | 153 | 我们现在使用新的 `staging_buffer` 和 `staging_buffer_memory` 来映射和复制顶点数据。在本章中,我们将使用两个新的缓冲用法标志: 154 | 155 | * `vk::BufferUsageFlags::TRANSFER_SRC` – 缓冲可以作为内存传输操作的源。 156 | * `vk::BufferUsageFlags::TRANSFER_DST` – 缓冲可以作为内存传输操作的目标。 157 | 158 | `vertex_buffer` 现在是从设备本地内存类型分配的,这通常意味着我们不能使用 `map_memory`。然而,我们可以将数据从 `staging_buffer` 复制到 `vertex_buffer`。我们必须为 `staging_buffer` 指定传输源标志,为 `vertex_buffer` 指定传输目标标志和顶点缓冲用法标志,来表明我们的意图。 159 | 160 | 接下来,我们将编写一个名为 `copy_buffer` 的函数,用于将内容从一个缓冲复制到另一个缓冲。 161 | 162 | ```rust,noplaypen 163 | unsafe fn copy_buffer( 164 | device: &Device, 165 | data: &AppData, 166 | source: vk::Buffer, 167 | destination: vk::Buffer, 168 | size: vk::DeviceSize, 169 | ) -> Result<()> { 170 | Ok(()) 171 | } 172 | ``` 173 | 174 | 内存传输操作与绘制指令一样,都需要通过指令缓冲来执行。因此,我们首先需要分配一个临时的指令缓冲。你可能希望为这些短暂的缓冲创建一个独立的指令池,因为实现可以对内存分配进行优化。在这种情况下,你应该在生成指令池时使用 `vk::CommandPoolCreateFlags::TRANSIENT` 标志。 175 | 176 | ```rust,noplaypen 177 | unsafe fn copy_buffer( 178 | device: &Device, 179 | data: &AppData, 180 | source: vk::Buffer, 181 | destination: vk::Buffer, 182 | size: vk::DeviceSize, 183 | ) -> Result<()> { 184 | let info = vk::CommandBufferAllocateInfo::builder() 185 | .level(vk::CommandBufferLevel::PRIMARY) 186 | .command_pool(data.command_pool) 187 | .command_buffer_count(1); 188 | 189 | let command_buffer = device.allocate_command_buffers(&info)?[0]; 190 | 191 | Ok(()) 192 | } 193 | ``` 194 | 195 | 然后开始记录指令缓冲: 196 | 197 | ```rust,noplaypen 198 | let info = vk::CommandBufferBeginInfo::builder() 199 | .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); 200 | 201 | device.begin_command_buffer(command_buffer, &info)?; 202 | ``` 203 | 204 | 我们将只使用这个指令缓冲一次,并在复制操作完成之前等待函数返回。使用 `vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT` 标志可以向驱动程序表明我们的意图,这是一个很好的实践。 205 | 206 | ```rust,noplaypen 207 | let regions = vk::BufferCopy::builder().size(size); 208 | device.cmd_copy_buffer(command_buffer, source, destination, &[regions]); 209 | ``` 210 | 211 | 缓冲的内容通过 `cmd_copy_buffer` 指令进行传输。该指令以源缓冲、目标缓冲和待复制区域的数组为参数。区域由 `vk::BufferCopy` 结构体定义,结构体中包括源缓冲偏移量、目标缓冲偏移量和大小。需要注意的是,与 `map_memory` 指令不同,这里不能指定 `vk::WHOLE_SIZE`。 212 | 213 | ```rust,noplaypen 214 | device.end_command_buffer(command_buffer)?; 215 | ``` 216 | 217 | 这个指令缓冲仅包含复制指令,因此我们在复制指令之后停止记录。现在执行该指令缓冲以完成传输操作: 218 | 219 | ```rust,noplaypen 220 | let command_buffers = &[command_buffer]; 221 | let info = vk::SubmitInfo::builder() 222 | .command_buffers(command_buffers); 223 | 224 | device.queue_submit(data.graphics_queue, &[info], vk::Fence::null())?; 225 | device.queue_wait_idle(data.graphics_queue)?; 226 | ``` 227 | 228 | 与绘制指令不同,这次我们无需等待事件,而是立即在缓冲上执行传输操作。同样,有两种方法可以等待传输完成。我们可以使用围栏(fence),并使用 `wait_for_fences` 来等待,或者只需使用 `queue_wait_idle` 等待传输队列变为空闲状态。使用围栏可以让你同时安排多个传输并等待它们全部完成,而不必逐个执行。这可以给驱动程序更多优化的机会。 229 | 230 | ```rust,noplaypen 231 | device.free_command_buffers(data.command_pool, &[command_buffer]); 232 | ``` 233 | 234 | 别忘记清理用于传输操作的指令缓冲。 235 | 236 | 现在,我们可以在 `create_vertex_buffer` 函数中调用 `copy_buffer`,将顶点数据复制到设备本地缓冲: 237 | 238 | ```rust,noplaypen 239 | copy_buffer(device, data, staging_buffer, vertex_buffer, size)?; 240 | ``` 241 | 242 | 在从暂存缓冲复制数据到设备缓冲之后,不要忘记进行清理: 243 | 244 | ```rust,noplaypen 245 | device.destroy_buffer(staging_buffer, None); 246 | device.free_memory(staging_buffer_memory, None); 247 | ``` 248 | 249 | 运行程序以验证你是否能再次看到熟悉的三角形。现在,顶点数据是从高性能内存加载的,尽管目前可能看不到改进。当我们开始渲染更复杂的几何图形时,这一点将变得更加重要。 250 | 251 | ## 结论 252 | 253 | 值得注意的是,在实际的应用程序中,你不应该为每个缓冲都调用 `allocate_memory`。内存分配的最大数量受到物理设备的 `max_memory_allocation_count` 限制,即使在高端硬件(如 NVIDIA GTX 1080)上,这个限制也可能低至 `4096`。要在同一时刻为大量对象分配内存,正确的方法是创建一个自定义的分配器,通过使用我们在许多函数中看到的 `offset` 参数,将单个分配分割为多个不同的对象。 254 | 255 | 然而,在本教程中可以为每个资源单独分配,因为目前我们不会接近这些限制。 256 | -------------------------------------------------------------------------------- /book/src/vertex/vertex_buffer_creation.md: -------------------------------------------------------------------------------- 1 | # 创建顶点缓冲 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/18_vertex_buffer.rs) 8 | 9 | 在 Vulkan 中,缓冲是用于存储可被显卡读取的任意数据的内存区域。我们会在本章中用它们来存储顶点数据,但它们也可以用于许多其他目的,这些将在以后的章节中探讨。与我们到目前为止见过的 Vulkan 对象不同,缓冲不会自动为自己分配内存。前面章节中的工作已经表明,Vulkan API 将几乎所有事物置于程序员的控制下,内存管理就是其中之一。 10 | 11 | > 尽管本教程只使用 Vulkan API 管理内存, 但现实世界中的许多 Vulkan 应用程序使用更高级的抽象,例如 [Vulkan 内存分配器](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)(VMA)。VMA 是一个库,它封装了 Vulkan API,并使内存管理变得更加简单和容易。[`vulkanalia-vma` crate](https://docs.rs/vulkanalia-vma)(`vulkanalia` 项目的一部分)提供了 VMA 与 `vulkanalia` 的集成。 12 | 13 | ## 创建缓冲 14 | 15 | 首先,我们创建一个名为 `create_vertex_buffer` 的新函数,并在 `App::create` 函数中,在 `create_command_buffers` 之前调用它。 16 | 17 | ```rust,noplaypen 18 | impl App { 19 | unsafe fn create(window: &Window) -> Result { 20 | // ... 21 | create_vertex_buffer(&instance, &device, &mut data)?; 22 | create_command_buffers(&device, &mut data)?; 23 | // ... 24 | } 25 | } 26 | 27 | unsafe fn create_vertex_buffer( 28 | instance: &Instance, 29 | device: &Device, 30 | data: &mut AppData, 31 | ) -> Result<()> { 32 | Ok(()) 33 | } 34 | ``` 35 | 36 | 创建缓冲需要填充一个 `vk::BufferCreateInfo` 结构体。 37 | 38 | ```rust,noplaypen 39 | let buffer_info = vk::BufferCreateInfo::builder() 40 | .size((size_of::() * VERTICES.len()) as u64) 41 | // continued... 42 | ``` 43 | 44 | 结构体的第一个字段是 `size` ,它指定缓冲的大小,以字节为单位。使用 `size_of` 可以很容易地计算出顶点数据的大小。 45 | 46 | ```rust,noplaypen 47 | .usage(vk::BufferUsageFlags::VERTEX_BUFFER) 48 | ``` 49 | 50 | 结构体的第二个字段是 `usage`,它表示缓冲中的数据将用于哪些目的。使用按位或可以指定多个目的。在当前的场景下我们会将缓冲用作顶点缓冲,关于其他类型的用法将在以后的章节中讨论。 51 | 52 | ```rust,noplaypen 53 | .sharing_mode(vk::SharingMode::EXCLUSIVE); 54 | ``` 55 | 56 | 和交换链中的图像一样,缓冲也既可以由特定的队列族拥有,或者在多个队列族之间共享。由于缓冲仅将在图形队列中使用,因此我们可以使用独占访问。 57 | 58 | ```rust,noplaypen 59 | .flags(vk::BufferCreateFlags::empty()); // 可选 60 | ``` 61 | 62 | `flags` 参数用于配置稀疏缓冲内存(sparse buffer memory),现在我们还不用关心这个。你可以省略这个字段,它会被自动设置为默认值(空标志集)。 63 | 64 | 现在,我们可以使用 `create_buffer` 创建缓冲。首先在 `AppData` 中添加一个 `vertex_buffer` 字段来保存缓冲句柄。 65 | 66 | ```rust,noplaypen 67 | struct AppData { 68 | // ... 69 | vertex_buffer: vk::Buffer, 70 | } 71 | ``` 72 | 73 | 接下来在 `create_vertex_buffer` 中调用 `create_buffer`: 74 | 75 | ```rust,noplaypen 76 | unsafe fn create_vertex_buffer( 77 | instance: &Instance, 78 | device: &Device, 79 | data: &mut AppData, 80 | ) -> Result<()> { 81 | let buffer_info = vk::BufferCreateInfo::builder() 82 | .size((size_of::() * VERTICES.len()) as u64) 83 | .usage(vk::BufferUsageFlags::VERTEX_BUFFER) 84 | .sharing_mode(vk::SharingMode::EXCLUSIVE); 85 | 86 | data.vertex_buffer = device.create_buffer(&buffer_info, None)?; 87 | 88 | Ok(()) 89 | } 90 | ``` 91 | 92 | 缓冲应该在程序结束之前在渲染指令中保持可用,并且缓冲不依赖于交换链,因此我们将在 `App::destroy` 方法中清理它: 93 | 94 | ```rust,noplaypen 95 | unsafe fn destroy(&mut self) { 96 | self.destroy_swapchain(); 97 | self.device.destroy_buffer(self.data.vertex_buffer, None); 98 | // ... 99 | } 100 | ``` 101 | 102 | ## 内存需求 103 | 104 | 缓冲已经创建了,但实际上我们还没有为其分配任何内存。为缓冲分配内存的第一步是使用 `get_buffer_memory_requirements` 函数查询其内存需求。 105 | 106 | ```rust,noplaypen 107 | let requirements = device.get_buffer_memory_requirements(data.vertex_buffer); 108 | ``` 109 | 110 | 这个函数返回的 `vk::MemoryRequirements` 结构体有三个字段: 111 | 112 | * `size` – 所需内存大小(以字节为单位),可能与 `buffer_info.size` 不同。 113 | * `alignment` – 缓冲在内存分配的区域中开始的偏移量(以字节为单位),取决于 `buffer_info.usage` 和 `buffer_info.flags`。 114 | * `memory_type_bits` – 适用于缓冲的内存类型。 115 | 116 | 显卡可以分配不同类型的内存,每种类型的内存在允许的操作和性能特性方面各不相同。我们需要将缓冲的需求(`vk::MemoryRequirements`)和我们应用程序的需求结合起来,找到合适的内存类型。为此,我们创建一个新函数 `get_memory_type_index`。 117 | 118 | ```rust,noplaypen 119 | unsafe fn get_memory_type_index( 120 | instance: &Instance, 121 | data: &AppData, 122 | properties: vk::MemoryPropertyFlags, 123 | requirements: vk::MemoryRequirements, 124 | ) -> Result { 125 | } 126 | ``` 127 | 128 | 首先,我们需要使用 `get_physical_device_memory_properties` 查询设备上可用的内存类型。 129 | 130 | ```rust,noplaypen 131 | let memory = instance.get_physical_device_memory_properties(data.physical_device); 132 | ``` 133 | 134 | 返回的 `vk::PhysicalDeviceMemoryProperties` 结构体有两个数组 `memory_types` 和 `memory_heaps`。内存堆代表不同的内存资源,比如专用的 VRAM 和在 VRAM 耗尽时 RAM 中的交换空间。这些堆中有不同类型的内存。现在我们只关注内存类型,而不关注内存来自哪个堆,但你应该能想到不同的堆会影响性能。 135 | 136 | 首先,让我们找到一个对缓冲本身合适的内存类型: 137 | 138 | ```rust,noplaypen 139 | (0..memory.memory_type_count) 140 | .find(|i| (requirements.memory_type_bits & (1 << i)) != 0) 141 | .ok_or_else(|| anyhow!("Failed to find suitable memory type.")) 142 | ``` 143 | 144 | `requirements` 参数中的 `memory_type_bits` 字段将被用于指定适合的内存类型。这意味着我们可以通过简单地迭代并检查相应的位是否设置为 `1` 来找到适合的内存类型的索引。 145 | 146 | 然而,内存类型不仅要对顶点缓冲合适,我们还需要能够将顶点数据写入该内存。`memory_types` 数组由 `vk::MemoryType` 结构体组成,该结构体指定每种类型内存的堆(heap)和属性(properties)。属性定义了内存的特殊特性,例如能否从 CPU 映射它以便我们从 CPU 写入数据 —— 这个属性通过 `vk::MemoryPropertyFlags::HOST_VISIBLE` 来指示。我们还需要使用 `vk::MemoryPropertyFlags::HOST_COHERENT` 属性。我们将在映射内存时看到为什么需要这样做。 147 | 148 | 现在,修改循环以检查此属性的支持: 149 | 150 | ```rust,noplaypen 151 | (0..memory.memory_type_count) 152 | .find(|i| { 153 | let suitable = (requirements.memory_type_bits & (1 << i)) != 0; 154 | let memory_type = memory.memory_types[*i as usize]; 155 | suitable && memory_type.property_flags.contains(properties) 156 | }) 157 | .ok_or_else(|| anyhow!("Failed to find suitable memory type.")) 158 | ``` 159 | 160 | 如果存在适合缓冲的内存类型,并且该内存类型具有我们所需的所有属性,则返回其索引;否则返回错误。 161 | 162 | ## 内存分配 163 | 164 | 现在我们已经有了确定正确内存类型的方法,我们可以填充 `vk::MemoryAllocateInfo` 结构体来实际分配内存了。 165 | 166 | ```rust,noplaypen 167 | let memory_info = vk::MemoryAllocateInfo::builder() 168 | .allocation_size(requirements.size) 169 | .memory_type_index(get_memory_type_index( 170 | instance, 171 | data, 172 | vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE, 173 | requirements, 174 | )?); 175 | ``` 176 | 177 | 内存分配就是简单地指定大小和类型,这两者都来自于顶点缓冲的内存需求和所需的属性。在 `AppData` 中添加一个字段来存储内存句柄: 178 | 179 | ```rust,noplaypen 180 | struct AppData { 181 | // ... 182 | vertex_buffer: vk::Buffer, 183 | vertex_buffer_memory: vk::DeviceMemory, 184 | } 185 | ``` 186 | 187 | 调用 `allocate_memory` 来填充这个新字段: 188 | 189 | ```rust,noplaypen 190 | data.vertex_buffer_memory = device.allocate_memory(&memory_info, None)?; 191 | ``` 192 | 193 | 如果内存分配成功,我们就可以使用 `bind_buffer_memory` 将内存与缓冲关联起来: 194 | 195 | ```rust,noplaypen 196 | device.bind_buffer_memory(data.vertex_buffer, data.vertex_buffer_memory, 0)?; 197 | ``` 198 | 199 | 前两个参数不言自明,第三个参数是顶点数据在内存区域内的偏移量。由于此内存专门为顶点缓冲分配,因此偏移量是 `0`。如果我们要提供非零的偏移量,则这个值必须可被 `requirements.alignment` 整除。 200 | 201 | 当然,就像在 C 语言中动态分配的内存一样,内存应该在某个时候被释放。绑定到缓冲对象的内存在缓冲不再被使用时可以被释放,所以让我们在缓冲被销毁后释放它: 202 | 203 | ```rust,noplaypen 204 | unsafe fn destroy(&mut self) { 205 | self.destroy_swapchain(); 206 | self.device.destroy_buffer(self.data.vertex_buffer, None); 207 | self.device.free_memory(self.data.vertex_buffer_memory, None); 208 | // ... 209 | } 210 | ``` 211 | 212 | ## 填充顶点缓冲 213 | 214 | 现在是时候将顶点数据复制到缓冲了,这是使用 `map_memory` 函数通过将缓冲内存映射到 CPU 可访问的内存中来完成的。 215 | 216 | ```rust,noplaypen 217 | let memory = device.map_memory( 218 | data.vertex_buffer_memory, 219 | 0, 220 | buffer_info.size, 221 | vk::MemoryMapFlags::empty(), 222 | )?; 223 | ``` 224 | 225 | 该函数允许我们访问由偏移量和大小指定的内存区域。在这里,偏移量和大小分别为 `0` 和 `buffer_info.size`。还可以使用特殊值 `vk::WHOLE_SIZE` 来映射所有内存。最后一个参数可用于指定标志,但当前 API 中还没有任何可用的标志。它必须设置为空标志集。返回的值是映射值的指针。 226 | 227 | 在继续之前,我们需要一个将顶点列表的内存复制到映射内存中的函数。在程序中添加这个导入: 228 | 229 | ```rust,noplaypen 230 | use std::ptr::copy_nonoverlapping as memcpy; 231 | ``` 232 | 233 | 现在我们可以将顶点数据复制到缓冲内存中,然后使用 `unmap_memory` 取消映射。 234 | 235 | ```rust,noplaypen 236 | memcpy(VERTICES.as_ptr(), memory.cast(), VERTICES.len()); 237 | device.unmap_memory(data.vertex_buffer_memory); 238 | ``` 239 | 240 | 不幸的是,出于诸如缓存(caching)的原因,驱动程序可能不会立即将数据复制到缓冲内存中。写入缓冲的数据亦可能在映射内存中尚不可见。有两种方法可以解决这个问题: 241 | 242 | * 使用主机一致(host coherent)的内存堆,这种堆使用 `vk::MemoryPropertyFlags::HOST_COHERENT` 表示 243 | * 在写入映射内存后调用 `flush_mapped_memory_ranges`,并在读取映射内存之前调用 `invalidate_mapped_memory_ranges` 244 | 245 | 我们采用了第一种方法,这样可以确保映射内存始终与分配的内存内容相匹配。相较于冲刷(flush)内存而言,这样做性能稍差,但我们将在下一章看到为什么这没关系。 246 | 247 | 冲刷内存范围或使用一致性内存堆意味着驱动程序将知道我们对缓冲的写入,但这并不意味着我们写入的数据实际上已经在 GPU 上可见。将数据传输到 GPU 是在后台进行的操作,规范仅保证这个操作在我们下一次调用 `queue_submit` 时是完成的。 248 | 249 | ## 绑定顶点缓冲 250 | 251 | 现在,仅剩的任务是在渲染操作期间绑定顶点缓冲。我们将扩展 `create_command_buffers` 函数来完成这个任务。 252 | 253 | ```rust,noplaypen 254 | // ... 255 | device.cmd_bind_vertex_buffers(*command_buffer, 0, &[data.vertex_buffer], &[0]); 256 | device.cmd_draw(*command_buffer, VERTICES.len() as u32, 1, 0, 0); 257 | // ... 258 | ``` 259 | 260 | `cmd_bind_vertex_buffers` 函数用于将顶点缓冲绑定到绑定点,就像我们在上一章中设置的那样。第二个参数指定我们正在使用的顶点输入绑定的索引。最后两个参数指定要绑定的顶点缓冲和从中开始读取顶点数据的字节偏移量。你还应该更改对 `cmd_draw` 的调用,将缓冲中的顶点数传递给该函数,代替原先硬编码的数字 `3`。 261 | 262 | 现在运行程序,你应该会再次看到熟悉的三角形: 263 | 264 | ![三角形](../images/triangle.png) 265 | 266 | 通过修改 `VERTICES` 列表,将顶点的颜色更改为白色,可以尝试修改三角形的顶点颜色: 267 | 268 | ```rust,noplaypen 269 | static VERTICES: [Vertex; 3] = [ 270 | Vertex::new(vec2(0.0, -0.5), vec3(1.0, 1.0, 1.0)), 271 | Vertex::new(vec2(0.5, 0.5), vec3(0.0, 1.0, 0.0)), 272 | Vertex::new(vec2(-0.5, 0.5), vec3(0.0, 0.0, 1.0)), 273 | ]; 274 | ``` 275 | 276 | 再次运行程序,你应该会看到以下效果: 277 | 278 | ![白色三角](../images/triangle_white.png) 279 | 280 | 在下一章中,我们将介绍另一种将顶点数据复制到顶点缓冲的方法。这种方法能带来更好的性能,但需要更多的工作。 281 | -------------------------------------------------------------------------------- /book/src/vertex/vertex_input_description.md: -------------------------------------------------------------------------------- 1 | # 描述顶点输入 2 | 3 | > 原文链接: 4 | > 5 | > Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd 6 | 7 | **本章代码:**[main.rs](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/src/17_vertex_input.rs) | [shader.vert](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/shaders/17/shader.vert) | [shader.frag](https://github.com/chuigda/Vulkan-Tutorial-Rust-CN/tree/master/shaders/17/shader.frag) 8 | 9 | 在接下来的几章中,我们将用内存中的顶点缓冲替换顶点着色器中硬编码的顶点数据。我们将从最简单的方法开始,即创建一个对 CPU 可见的缓冲,并直接将顶点数据复制到其中。之后,我们将学习如何使用暂存缓冲将顶点数据复制到高性能内存中。 10 | 11 | ## 顶点着色器 12 | 13 | 首先修改顶点着色器,不再在着色器代码本身中包含顶点数据。顶点着色器将使用 `in` 关键字从顶点缓冲中获取输入。 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 | `inPosition` 和 `inColor` 变量是*顶点属性*。它们是在顶点缓冲中为每个顶点指定的属性,就像我们之前手动使用两个数组为每个顶点指定了位置和颜色一样。记得重新编译顶点着色器! 30 | 31 | 与 `fragColor` 类似,`layout(location = x)` 注解为输入变量分配了索引,我们稍后可以用索引来引用这些变量。重要的是要知道,某些类型(例如 64 位的 `dvec3` 向量)使用多个*槽位*。这意味着在它之后的索引必须至少 +2: 32 | 33 | ```glsl 34 | layout(location = 0) in dvec3 inPosition; 35 | layout(location = 2) in vec3 inColor; 36 | ``` 37 | 38 | 你可以在 [OpenGL wiki](https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)) 中找到关于布局限定符的更多信息。 39 | 40 | ## 顶点数据 41 | 42 | 我们将从着色器代码中将顶点数据移到程序代码的数组中。首先,向我们的程序添加几个导入项和一些类型别名。 43 | 44 | ```rust,noplaypen 45 | use std::mem::size_of; 46 | 47 | use cgmath::{vec2, vec3}; 48 | 49 | type Vec2 = cgmath::Vector2; 50 | type Vec3 = cgmath::Vector3; 51 | ``` 52 | 53 | `size_of` 函数将用于计算我们将要定义的顶点数据的大小,而 `cgmath` 则定义了我们需要的向量类型。 54 | 55 | 接下来,创建一个名为 `Vertex` 的 `#[repr(C)]` 结构体,其中包含我们将在顶点着色器中使用的两个属性,并添加一个简单的构造函数: 56 | 57 | ```rust,noplaypen 58 | #[repr(C)] 59 | #[derive(Copy, Clone, Debug)] 60 | struct Vertex { 61 | pos: Vec2, 62 | color: Vec3, 63 | } 64 | 65 | impl Vertex { 66 | const fn new(pos: Vec2, color: Vec3) -> Self { 67 | Self { pos, color } 68 | } 69 | } 70 | ``` 71 | 72 | `cgmath` 提供了与着色器语言中使用的向量类型完全匹配的 Rust 类型。 73 | 74 | ```rust,noplaypen 75 | static VERTICES: [Vertex; 3] = [ 76 | Vertex::new(vec2(0.0, -0.5), vec3(1.0, 0.0, 0.0)), 77 | Vertex::new(vec2(0.5, 0.5), vec3(0.0, 1.0, 0.0)), 78 | Vertex::new(vec2(-0.5, 0.5), vec3(0.0, 0.0, 1.0)), 79 | ]; 80 | ``` 81 | 82 | 现在我们可以使用 `Vertex` 结构体来定义顶点数据了。我们使用了与之前完全相同的位置和颜色值,但现在顶点位置和颜色数据被合并进一个顶点数组中。这种定义顶点数据的方式也被称为*交错顶点属性*(interleaving vertex attributes)。 83 | 84 | ## 绑定描述 85 | 86 | 接下来的步骤是告诉 Vulkan 在顶点数据上传到 GPU 内存后如何将这些数据传递给顶点着色器。我们需要两种结构体来传递这些信息。 87 | 88 | 第一种结构体是顶点绑定 `vk::VertexInputBindingDescription`,我们为 `Vertex` 结构体添加一个方法来填充它: 89 | 90 | ```rust,noplaypen 91 | impl Vertex { 92 | fn binding_description() -> vk::VertexInputBindingDescription { 93 | } 94 | } 95 | ``` 96 | 97 | 98 | 顶点绑定描述了从内存中加载数据的方式:它指定了数据条目之间的字节数,以及是在每个顶点后移动到下一个数据条目,还是在每个实例后移动到下一个数据条目。 99 | 100 | ```rust,noplaypen 101 | vk::VertexInputBindingDescription::builder() 102 | .binding(0) 103 | .stride(size_of::() as u32) 104 | .input_rate(vk::VertexInputRate::VERTEX) 105 | .build() 106 | ``` 107 | 108 | 我们的每个顶点的数据都被紧密地打包在一个数组中,因此我们只会有一个绑定。`binding` 参数指定绑定在绑定数组中的索引。`stride` 参数指定从一个条目到下一个条目的字节数,而 `input_rate` 参数可以有以下值之一: 109 | 110 | * `vk::VertexInputRate::VERTEX` – 在每个顶点后移动到下一个数据条目 111 | * `vk::VertexInputRate::INSTANCE` – 在每个实例后移动到下一个数据条目 112 | 113 | 由于我们不会使用实例化渲染(instanced rendering),因此我们将使用每个顶点的数据。 114 | 115 | ## 属性描述 116 | 117 | 第二种结构体是 `vk::VertexInputAttributeDescription`,它用于描述顶点输入。我们将为 `Vertex` 结构体添加另一个辅助方法来填充这些结构。 118 | 119 | ```rust,noplaypen 120 | impl Vertex { 121 | fn attribute_descriptions() -> [vk::VertexInputAttributeDescription; 2] { 122 | } 123 | } 124 | ``` 125 | 126 | 如函数原型所示,我们将会创建两个这样的结构体。这个结构体描述了如何从顶点绑定产生的顶点数据块中提取顶点属性。我们有两个属性:位置和颜色,因此我们需要两个结构体。 127 | 128 | ```rust,noplaypen 129 | let pos = vk::VertexInputAttributeDescription::builder() 130 | .binding(0) 131 | .location(0) 132 | .format(vk::Format::R32G32_SFLOAT) 133 | .offset(0) 134 | .build(); 135 | ``` 136 | 137 | `binding` 参数告诉 Vulkan 顶点数据来自哪个绑定。`location` 参数引用了顶点着色器中输入的 `location` 指令。顶点着色器中顶点位置的 `location` 是 `0`,它有两个 32 位浮点分量。 138 | 139 | `format` 参数描述属性的数据类型。有点混淆的是,`format` 字段使用与颜色格式相同的枚举类型。以下是常见的着色器类型和对应的颜色格式枚举: 140 | 141 | * `f32` – `vk::Format::R32_SFLOAT`  142 | * `cgmath::Vector2` (我们的 `Vec2`) – `vk::Format::R32G32_SFLOAT`  143 | * `cgmath::Vector3` (我们的 `Vec3`) – `vk::Format::R32G32B32_SFLOAT`  144 | * `cgmath::Vector4` – `vk::Format::R32G32B32A32_SFLOAT`  145 | 146 | 如你所见,颜色格式的颜色通道数量应与着色器数据类型的分量数量相匹配。颜色格式的通道数量比着色器数据类型的分量数量多也是可以的,但多余的通道将被静默丢弃。如果通道数量少于分量数量,则 BGA 分量将使用默认值 `(0, 0, 1)` 。颜色类型( `SFLOAT` ,`UINT` ,`SINT` )和位宽度也应与着色器数据类型匹配。请参阅以下示例: 147 | 148 | * `cgmath::Vector2` – `vk::Format::R32G32_SINT`,包含 `i32` 的 2 分量向量 149 | * `cgmath::Vector4` – `vk::Format::R32G32B32A32_UINT`,包含 `u32` 的 4 分量向量 150 | * `f64` – `vk::Format::R64_SFLOAT`,双精度(64位)浮点数 151 | 152 | `format` 参数隐式地定义了属性数据的字节大小,而 `offset` 参数指定从顶点数据开始的字节数:绑定每次加载一个 `Vertex`,位置属性(`pos`)从该结构体的开始处偏移 `0` 字节。 153 | 154 | ```rust,noplaypen 155 | let color = vk::VertexInputAttributeDescription::builder() 156 | .binding(0) 157 | .location(1) 158 | .format(vk::Format::R32G32B32_SFLOAT) 159 | .offset(size_of::() as u32) 160 | .build(); 161 | ``` 162 | 163 | 颜色属性的描述方式基本相同。 164 | 165 | 最后,构造要从辅助方法返回的数组: 166 | 167 | ```rust,noplaypen 168 | [pos, color] 169 | ``` 170 | 171 | ## 管线顶点输入 172 | 173 | 现在我们需要在 `create_pipeline` 中引用这两个描述结构体,以让图形管线接受这种格式的顶点数据。找到 `vertex_input_state` 结构并修改它,让它引用这两个描述结构体: 174 | 175 | ```rust,noplaypen 176 | let binding_descriptions = &[Vertex::binding_description()]; 177 | let attribute_descriptions = Vertex::attribute_descriptions(); 178 | let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::builder() 179 | .vertex_binding_descriptions(binding_descriptions) 180 | .vertex_attribute_descriptions(&attribute_descriptions); 181 | ``` 182 | 183 | 现在,管线已准备好接受 `vertices` 容器格式的顶点数据,并将其传递给我们的顶点着色器。如果你现在启用了校验层并运行程序,你会看到它抱怨没有顶点缓冲被绑定到绑定点。接下来的步骤是创建一个顶点缓冲,并将顶点数据移到其中,以便 GPU 能够访问它。 184 | -------------------------------------------------------------------------------- /book/vulkan-concepts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/book/vulkan-concepts.png -------------------------------------------------------------------------------- /resources/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/resources/texture.png -------------------------------------------------------------------------------- /resources/viking_room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/resources/viking_room.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | -------------------------------------------------------------------------------- /shaders/09/frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/09/frag.spv -------------------------------------------------------------------------------- /shaders/09/shader.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 | -------------------------------------------------------------------------------- /shaders/09/shader.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 | -------------------------------------------------------------------------------- /shaders/09/vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/09/vert.spv -------------------------------------------------------------------------------- /shaders/17/frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/17/frag.spv -------------------------------------------------------------------------------- /shaders/17/shader.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 | -------------------------------------------------------------------------------- /shaders/17/shader.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 | -------------------------------------------------------------------------------- /shaders/17/vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/17/vert.spv -------------------------------------------------------------------------------- /shaders/21/frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/21/frag.spv -------------------------------------------------------------------------------- /shaders/21/shader.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 | -------------------------------------------------------------------------------- /shaders/21/shader.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 | -------------------------------------------------------------------------------- /shaders/21/vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/21/vert.spv -------------------------------------------------------------------------------- /shaders/25/frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/25/frag.spv -------------------------------------------------------------------------------- /shaders/25/shader.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 | -------------------------------------------------------------------------------- /shaders/25/shader.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 | -------------------------------------------------------------------------------- /shaders/25/vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/25/vert.spv -------------------------------------------------------------------------------- /shaders/26/frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/26/frag.spv -------------------------------------------------------------------------------- /shaders/26/shader.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 | -------------------------------------------------------------------------------- /shaders/26/shader.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 | -------------------------------------------------------------------------------- /shaders/26/vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/26/vert.spv -------------------------------------------------------------------------------- /shaders/30/frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/30/frag.spv -------------------------------------------------------------------------------- /shaders/30/shader.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 1) uniform sampler2D texSampler; 4 | 5 | layout(push_constant) uniform PushConstants { 6 | layout(offset = 64) float opacity; 7 | } pcs; 8 | 9 | layout(location = 0) in vec3 fragColor; 10 | layout(location = 1) in vec2 fragTexCoord; 11 | 12 | layout(location = 0) out vec4 outColor; 13 | 14 | void main() { 15 | outColor = vec4(texture(texSampler, fragTexCoord).rgb, pcs.opacity); 16 | } 17 | -------------------------------------------------------------------------------- /shaders/30/shader.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 0) uniform UniformBufferObject { 4 | mat4 view; 5 | mat4 proj; 6 | } ubo; 7 | 8 | layout(push_constant) uniform PushConstants { 9 | mat4 model; 10 | } pcs; 11 | 12 | layout(location = 0) in vec3 inPosition; 13 | layout(location = 1) in vec3 inColor; 14 | layout(location = 2) in vec2 inTexCoord; 15 | 16 | layout(location = 0) out vec3 fragColor; 17 | layout(location = 1) out vec2 fragTexCoord; 18 | 19 | void main() { 20 | gl_Position = ubo.proj * ubo.view * pcs.model * vec4(inPosition, 1.0); 21 | fragColor = inColor; 22 | fragTexCoord = inTexCoord; 23 | } 24 | -------------------------------------------------------------------------------- /shaders/30/vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuigda/Vulkan-Tutorial-Rust-CN/3ab48bd2ee27996d7d85620ed57c1e8e2bff4038/shaders/30/vert.spv -------------------------------------------------------------------------------- /site/googlea55b23224d999983.html: -------------------------------------------------------------------------------- 1 | google-site-verification: googlea55b23224d999983.html -------------------------------------------------------------------------------- /site/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | https://vk.7dg.tech/ 6 | 2023-10-05T21:20:17+00:00 7 | 1.00 8 | 9 | 10 | https://vk.7dg.tech/introduction.html 11 | 2023-10-05T21:20:17+00:00 12 | 0.80 13 | 14 | 15 | https://vk.7dg.tech/overview.html 16 | 2023-10-05T21:20:17+00:00 17 | 0.80 18 | 19 | 20 | https://vk.7dg.tech/development_environment.html 21 | 2023-10-05T21:20:17+00:00 22 | 0.80 23 | 24 | 25 | https://vk.7dg.tech/faq.html 26 | 2023-10-05T21:20:17+00:00 27 | 0.80 28 | 29 | 30 | https://vk.7dg.tech/setup/base_code.html 31 | 2023-10-05T21:20:17+00:00 32 | 0.80 33 | 34 | 35 | https://vk.7dg.tech/setup/instance.html 36 | 2023-10-05T21:20:17+00:00 37 | 0.80 38 | 39 | 40 | https://vk.7dg.tech/setup/validation_layers.html 41 | 2023-10-05T21:20:17+00:00 42 | 0.80 43 | 44 | 45 | https://vk.7dg.tech/setup/physical_devices_and_queue_families.html 46 | 2023-10-05T21:20:17+00:00 47 | 0.80 48 | 49 | 50 | https://vk.7dg.tech/setup/logical_device_and_queues.html 51 | 2023-10-05T21:20:17+00:00 52 | 0.80 53 | 54 | 55 | https://vk.7dg.tech/presentation/window_surface.html 56 | 2023-10-05T21:20:17+00:00 57 | 0.80 58 | 59 | 60 | https://vk.7dg.tech/presentation/swapchain.html 61 | 2023-10-05T21:20:17+00:00 62 | 0.80 63 | 64 | 65 | https://vk.7dg.tech/presentation/image_views.html 66 | 2023-10-05T21:20:17+00:00 67 | 0.80 68 | 69 | 70 | https://vk.7dg.tech/pipeline/introduction.html 71 | 2023-10-05T21:20:17+00:00 72 | 0.80 73 | 74 | 75 | https://vk.7dg.tech/pipeline/shader_modules.html 76 | 2023-10-05T21:20:17+00:00 77 | 0.80 78 | 79 | 80 | https://vk.7dg.tech/pipeline/fixed_functions.html 81 | 2023-10-05T21:20:17+00:00 82 | 0.80 83 | 84 | 85 | https://vk.7dg.tech/pipeline/render_passes.html 86 | 2023-10-05T21:20:17+00:00 87 | 0.80 88 | 89 | 90 | https://vk.7dg.tech/pipeline/conclusion.html 91 | 2023-10-05T21:20:17+00:00 92 | 0.80 93 | 94 | 95 | https://vk.7dg.tech/drawing/framebuffers.html 96 | 2023-10-05T21:20:17+00:00 97 | 0.80 98 | 99 | 100 | https://vk.7dg.tech/drawing/command_buffers.html 101 | 2023-10-05T21:20:17+00:00 102 | 0.80 103 | 104 | 105 | https://vk.7dg.tech/drawing/rendering_and_presentation.html 106 | 2023-10-05T21:20:17+00:00 107 | 0.80 108 | 109 | 110 | https://vk.7dg.tech/swapchain/recreation.html 111 | 2023-10-05T21:20:17+00:00 112 | 0.80 113 | 114 | 115 | https://vk.7dg.tech/vertex/vertex_input_description.html 116 | 2023-10-05T21:20:17+00:00 117 | 0.80 118 | 119 | 120 | https://vk.7dg.tech/vertex/vertex_buffer_creation.html 121 | 2023-10-05T21:20:17+00:00 122 | 0.80 123 | 124 | 125 | https://vk.7dg.tech/vertex/staging_buffer.html 126 | 2023-10-05T21:20:17+00:00 127 | 0.80 128 | 129 | 130 | https://vk.7dg.tech/vertex/index_buffer.html 131 | 2023-10-05T21:20:17+00:00 132 | 0.80 133 | 134 | 135 | https://vk.7dg.tech/uniform/descriptor_set_layout_and_buffer.html 136 | 2023-10-05T21:20:17+00:00 137 | 0.80 138 | 139 | 140 | https://vk.7dg.tech/uniform/descriptor_pool_and_sets.html 141 | 2023-10-05T21:20:17+00:00 142 | 0.80 143 | 144 | 145 | https://vk.7dg.tech/texture/images.html 146 | 2023-10-05T21:20:17+00:00 147 | 0.80 148 | 149 | 150 | https://vk.7dg.tech/texture/image_view_and_sampler.html 151 | 2023-10-05T21:20:17+00:00 152 | 0.80 153 | 154 | 155 | https://vk.7dg.tech/texture/combined_image_sampler.html 156 | 2023-10-05T21:20:17+00:00 157 | 0.80 158 | 159 | 160 | https://vk.7dg.tech/model/depth_buffering.html 161 | 2023-10-05T21:20:17+00:00 162 | 0.80 163 | 164 | 165 | https://vk.7dg.tech/model/loading_models.html 166 | 2023-10-05T21:20:17+00:00 167 | 0.80 168 | 169 | 170 | https://vk.7dg.tech/quality/generating_mipmaps.html 171 | 2023-10-05T21:20:17+00:00 172 | 0.80 173 | 174 | 175 | https://vk.7dg.tech/quality/multisampling.html 176 | 2023-10-05T21:20:17+00:00 177 | 0.80 178 | 179 | 180 | https://vk.7dg.tech/dynamic/push_constants.html 181 | 2023-10-05T21:20:17+00:00 182 | 0.80 183 | 184 | 185 | https://vk.7dg.tech/dynamic/recycling_command_buffers.html 186 | 2023-10-05T21:20:17+00:00 187 | 0.80 188 | 189 | 190 | https://vk.7dg.tech/dynamic/secondary_command_buffers.html 191 | 2023-10-05T21:20:17+00:00 192 | 0.80 193 | 194 | 195 | https://vk.7dg.tech/conclusion.html 196 | 2023-10-05T21:20:17+00:00 197 | 0.80 198 | 199 | -------------------------------------------------------------------------------- /src/00_base_code.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | #![allow( 4 | dead_code, 5 | unused_variables, 6 | clippy::manual_slice_size_calculation, 7 | clippy::too_many_arguments, 8 | clippy::unnecessary_wraps 9 | )] 10 | 11 | use anyhow::Result; 12 | use winit::dpi::LogicalSize; 13 | use winit::event::{Event, WindowEvent}; 14 | use winit::event_loop::EventLoop; 15 | use winit::window::{Window, WindowBuilder}; 16 | 17 | #[rustfmt::skip] 18 | fn main() -> Result<()> { 19 | pretty_env_logger::init(); 20 | 21 | // Window 22 | 23 | let event_loop = EventLoop::new()?; 24 | let window = WindowBuilder::new() 25 | .with_title("Vulkan Tutorial (Rust)") 26 | .with_inner_size(LogicalSize::new(1024, 768)) 27 | .build(&event_loop)?; 28 | 29 | // App 30 | 31 | let mut app = unsafe { App::create(&window)? }; 32 | event_loop.run(move |event, elwt| { 33 | match event { 34 | // Request a redraw when all events were processed. 35 | Event::AboutToWait => window.request_redraw(), 36 | Event::WindowEvent { event, .. } => match event { 37 | // Render a frame if our Vulkan app is not being destroyed. 38 | WindowEvent::RedrawRequested if !elwt.exiting() => unsafe { app.render(&window) }.unwrap(), 39 | // Destroy our Vulkan app. 40 | WindowEvent::CloseRequested => { 41 | elwt.exit(); 42 | unsafe { app.destroy(); } 43 | } 44 | _ => {} 45 | } 46 | _ => {} 47 | } 48 | })?; 49 | 50 | Ok(()) 51 | } 52 | 53 | /// Our Vulkan app. 54 | #[derive(Clone, Debug)] 55 | struct App {} 56 | 57 | impl App { 58 | /// Creates our Vulkan app. 59 | unsafe fn create(window: &Window) -> Result { 60 | Ok(Self {}) 61 | } 62 | 63 | /// Renders a frame for our Vulkan app. 64 | unsafe fn render(&mut self, window: &Window) -> Result<()> { 65 | Ok(()) 66 | } 67 | 68 | /// Destroys our Vulkan app. 69 | unsafe fn destroy(&mut self) {} 70 | } 71 | 72 | /// The Vulkan handles and associated properties used by our Vulkan app. 73 | #[derive(Clone, Debug, Default)] 74 | struct AppData {} 75 | -------------------------------------------------------------------------------- /src/01_instance_creation.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | #![allow( 4 | dead_code, 5 | unused_variables, 6 | clippy::manual_slice_size_calculation, 7 | clippy::too_many_arguments, 8 | clippy::unnecessary_wraps 9 | )] 10 | 11 | use anyhow::{anyhow, Result}; 12 | use log::*; 13 | use vulkanalia::loader::{LibloadingLoader, LIBRARY}; 14 | use vulkanalia::prelude::v1_0::*; 15 | use vulkanalia::window as vk_window; 16 | use vulkanalia::Version; 17 | use winit::dpi::LogicalSize; 18 | use winit::event::{Event, WindowEvent}; 19 | use winit::event_loop::EventLoop; 20 | use winit::window::{Window, WindowBuilder}; 21 | 22 | /// The Vulkan SDK version that started requiring the portability subset extension for macOS. 23 | const PORTABILITY_MACOS_VERSION: Version = Version::new(1, 3, 216); 24 | 25 | #[rustfmt::skip] 26 | fn main() -> Result<()> { 27 | pretty_env_logger::init(); 28 | 29 | // Window 30 | 31 | let event_loop = EventLoop::new()?; 32 | let window = WindowBuilder::new() 33 | .with_title("Vulkan Tutorial (Rust)") 34 | .with_inner_size(LogicalSize::new(1024, 768)) 35 | .build(&event_loop)?; 36 | 37 | // App 38 | 39 | let mut app = unsafe { App::create(&window)? }; 40 | event_loop.run(move |event, elwt| { 41 | match event { 42 | // Request a redraw when all events were processed. 43 | Event::AboutToWait => window.request_redraw(), 44 | Event::WindowEvent { event, .. } => match event { 45 | // Render a frame if our Vulkan app is not being destroyed. 46 | WindowEvent::RedrawRequested if !elwt.exiting() => unsafe { app.render(&window) }.unwrap(), 47 | // Destroy our Vulkan app. 48 | WindowEvent::CloseRequested => { 49 | elwt.exit(); 50 | unsafe { app.destroy(); } 51 | } 52 | _ => {} 53 | } 54 | _ => {} 55 | } 56 | })?; 57 | 58 | Ok(()) 59 | } 60 | 61 | /// Our Vulkan app. 62 | #[derive(Clone, Debug)] 63 | struct App { 64 | entry: Entry, 65 | instance: Instance, 66 | } 67 | 68 | impl App { 69 | /// Creates our Vulkan app. 70 | unsafe fn create(window: &Window) -> Result { 71 | let loader = LibloadingLoader::new(LIBRARY)?; 72 | let entry = Entry::new(loader).map_err(|b| anyhow!("{}", b))?; 73 | let instance = create_instance(window, &entry)?; 74 | Ok(Self { entry, instance }) 75 | } 76 | 77 | /// Renders a frame for our Vulkan app. 78 | unsafe fn render(&mut self, window: &Window) -> Result<()> { 79 | Ok(()) 80 | } 81 | 82 | /// Destroys our Vulkan app. 83 | unsafe fn destroy(&mut self) { 84 | self.instance.destroy_instance(None); 85 | } 86 | } 87 | 88 | /// The Vulkan handles and associated properties used by our Vulkan app. 89 | #[derive(Clone, Debug, Default)] 90 | struct AppData {} 91 | 92 | //================================================ 93 | // Instance 94 | //================================================ 95 | 96 | unsafe fn create_instance(window: &Window, entry: &Entry) -> Result { 97 | // Application Info 98 | 99 | let application_info = vk::ApplicationInfo::builder() 100 | .application_name(b"Vulkan Tutorial (Rust)\0") 101 | .application_version(vk::make_version(1, 0, 0)) 102 | .engine_name(b"No Engine\0") 103 | .engine_version(vk::make_version(1, 0, 0)) 104 | .api_version(vk::make_version(1, 0, 0)); 105 | 106 | // Extensions 107 | 108 | let mut extensions = vk_window::get_required_instance_extensions(window) 109 | .iter() 110 | .map(|e| e.as_ptr()) 111 | .collect::>(); 112 | 113 | // Required by Vulkan SDK on macOS since 1.3.216. 114 | let flags = if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION { 115 | info!("Enabling extensions for macOS portability."); 116 | extensions.push(vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr()); 117 | extensions.push(vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr()); 118 | vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR 119 | } else { 120 | vk::InstanceCreateFlags::empty() 121 | }; 122 | 123 | // Create 124 | 125 | let info = vk::InstanceCreateInfo::builder() 126 | .application_info(&application_info) 127 | .enabled_extension_names(&extensions) 128 | .flags(flags); 129 | 130 | Ok(entry.create_instance(&info, None)?) 131 | } 132 | -------------------------------------------------------------------------------- /src/02_validation_layers.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | #![allow( 4 | dead_code, 5 | unused_variables, 6 | clippy::manual_slice_size_calculation, 7 | clippy::too_many_arguments, 8 | clippy::unnecessary_wraps 9 | )] 10 | 11 | use std::collections::HashSet; 12 | use std::ffi::CStr; 13 | use std::os::raw::c_void; 14 | 15 | use anyhow::{anyhow, Result}; 16 | use log::*; 17 | use vulkanalia::loader::{LibloadingLoader, LIBRARY}; 18 | use vulkanalia::prelude::v1_0::*; 19 | use vulkanalia::window as vk_window; 20 | use vulkanalia::Version; 21 | use winit::dpi::LogicalSize; 22 | use winit::event::{Event, WindowEvent}; 23 | use winit::event_loop::EventLoop; 24 | use winit::window::{Window, WindowBuilder}; 25 | 26 | use vulkanalia::vk::ExtDebugUtilsExtension; 27 | 28 | /// Whether the validation layers should be enabled. 29 | const VALIDATION_ENABLED: bool = cfg!(debug_assertions); 30 | /// The name of the validation layers. 31 | const VALIDATION_LAYER: vk::ExtensionName = vk::ExtensionName::from_bytes(b"VK_LAYER_KHRONOS_validation"); 32 | 33 | /// The Vulkan SDK version that started requiring the portability subset extension for macOS. 34 | const PORTABILITY_MACOS_VERSION: Version = Version::new(1, 3, 216); 35 | 36 | #[rustfmt::skip] 37 | fn main() -> Result<()> { 38 | pretty_env_logger::init(); 39 | 40 | // Window 41 | 42 | let event_loop = EventLoop::new()?; 43 | let window = WindowBuilder::new() 44 | .with_title("Vulkan Tutorial (Rust)") 45 | .with_inner_size(LogicalSize::new(1024, 768)) 46 | .build(&event_loop)?; 47 | 48 | // App 49 | 50 | let mut app = unsafe { App::create(&window)? }; 51 | event_loop.run(move |event, elwt| { 52 | match event { 53 | // Request a redraw when all events were processed. 54 | Event::AboutToWait => window.request_redraw(), 55 | Event::WindowEvent { event, .. } => match event { 56 | // Render a frame if our Vulkan app is not being destroyed. 57 | WindowEvent::RedrawRequested if !elwt.exiting() => unsafe { app.render(&window) }.unwrap(), 58 | // Destroy our Vulkan app. 59 | WindowEvent::CloseRequested => { 60 | elwt.exit(); 61 | unsafe { app.destroy(); } 62 | } 63 | _ => {} 64 | } 65 | _ => {} 66 | } 67 | })?; 68 | 69 | Ok(()) 70 | } 71 | 72 | /// Our Vulkan app. 73 | #[derive(Clone, Debug)] 74 | struct App { 75 | entry: Entry, 76 | instance: Instance, 77 | data: AppData, 78 | } 79 | 80 | impl App { 81 | /// Creates our Vulkan app. 82 | unsafe fn create(window: &Window) -> Result { 83 | let loader = LibloadingLoader::new(LIBRARY)?; 84 | let entry = Entry::new(loader).map_err(|b| anyhow!("{}", b))?; 85 | let mut data = AppData::default(); 86 | let instance = create_instance(window, &entry, &mut data)?; 87 | Ok(Self { entry, instance, data }) 88 | } 89 | 90 | /// Renders a frame for our Vulkan app. 91 | unsafe fn render(&mut self, window: &Window) -> Result<()> { 92 | Ok(()) 93 | } 94 | 95 | /// Destroys our Vulkan app. 96 | #[rustfmt::skip] 97 | unsafe fn destroy(&mut self) { 98 | if VALIDATION_ENABLED { 99 | self.instance.destroy_debug_utils_messenger_ext(self.data.messenger, None); 100 | } 101 | 102 | self.instance.destroy_instance(None); 103 | } 104 | } 105 | 106 | /// The Vulkan handles and associated properties used by our Vulkan app. 107 | #[derive(Clone, Debug, Default)] 108 | struct AppData { 109 | // Debug 110 | messenger: vk::DebugUtilsMessengerEXT, 111 | } 112 | 113 | //================================================ 114 | // Instance 115 | //================================================ 116 | 117 | unsafe fn create_instance(window: &Window, entry: &Entry, data: &mut AppData) -> Result { 118 | // Application Info 119 | 120 | let application_info = vk::ApplicationInfo::builder() 121 | .application_name(b"Vulkan Tutorial (Rust)\0") 122 | .application_version(vk::make_version(1, 0, 0)) 123 | .engine_name(b"No Engine\0") 124 | .engine_version(vk::make_version(1, 0, 0)) 125 | .api_version(vk::make_version(1, 0, 0)); 126 | 127 | // Layers 128 | 129 | let available_layers = entry 130 | .enumerate_instance_layer_properties()? 131 | .iter() 132 | .map(|l| l.layer_name) 133 | .collect::>(); 134 | 135 | if VALIDATION_ENABLED && !available_layers.contains(&VALIDATION_LAYER) { 136 | return Err(anyhow!("Validation layer requested but not supported.")); 137 | } 138 | 139 | let layers = if VALIDATION_ENABLED { 140 | vec![VALIDATION_LAYER.as_ptr()] 141 | } else { 142 | Vec::new() 143 | }; 144 | 145 | // Extensions 146 | 147 | let mut extensions = vk_window::get_required_instance_extensions(window) 148 | .iter() 149 | .map(|e| e.as_ptr()) 150 | .collect::>(); 151 | 152 | // Required by Vulkan SDK on macOS since 1.3.216. 153 | let flags = if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION { 154 | info!("Enabling extensions for macOS portability."); 155 | extensions.push(vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr()); 156 | extensions.push(vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr()); 157 | vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR 158 | } else { 159 | vk::InstanceCreateFlags::empty() 160 | }; 161 | 162 | if VALIDATION_ENABLED { 163 | extensions.push(vk::EXT_DEBUG_UTILS_EXTENSION.name.as_ptr()); 164 | } 165 | 166 | // Create 167 | 168 | let mut info = vk::InstanceCreateInfo::builder() 169 | .application_info(&application_info) 170 | .enabled_layer_names(&layers) 171 | .enabled_extension_names(&extensions) 172 | .flags(flags); 173 | 174 | let mut debug_info = vk::DebugUtilsMessengerCreateInfoEXT::builder() 175 | .message_severity(vk::DebugUtilsMessageSeverityFlagsEXT::all()) 176 | .message_type( 177 | vk::DebugUtilsMessageTypeFlagsEXT::GENERAL 178 | | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION 179 | | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, 180 | ) 181 | .user_callback(Some(debug_callback)); 182 | 183 | if VALIDATION_ENABLED { 184 | info = info.push_next(&mut debug_info); 185 | } 186 | 187 | let instance = entry.create_instance(&info, None)?; 188 | 189 | // Messenger 190 | 191 | if VALIDATION_ENABLED { 192 | data.messenger = instance.create_debug_utils_messenger_ext(&debug_info, None)?; 193 | } 194 | 195 | Ok(instance) 196 | } 197 | 198 | extern "system" fn debug_callback( 199 | severity: vk::DebugUtilsMessageSeverityFlagsEXT, 200 | type_: vk::DebugUtilsMessageTypeFlagsEXT, 201 | data: *const vk::DebugUtilsMessengerCallbackDataEXT, 202 | _: *mut c_void, 203 | ) -> vk::Bool32 { 204 | let data = unsafe { *data }; 205 | let message = unsafe { CStr::from_ptr(data.message) }.to_string_lossy(); 206 | 207 | if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::ERROR { 208 | error!("({:?}) {}", type_, message); 209 | } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING { 210 | warn!("({:?}) {}", type_, message); 211 | } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::INFO { 212 | debug!("({:?}) {}", type_, message); 213 | } else { 214 | trace!("({:?}) {}", type_, message); 215 | } 216 | 217 | vk::FALSE 218 | } 219 | -------------------------------------------------------------------------------- /src/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import os 5 | import sys 6 | import subprocess 7 | 8 | sources_dir = os.path.dirname(os.path.realpath(__file__)) 9 | sources = [p for p in os.listdir(sources_dir) if p.endswith(".rs")] 10 | sources_by_prefix = dict([(int(s.split("_")[0]), s) for s in sources]) 11 | 12 | #================================================= 13 | # Compare 14 | #================================================= 15 | 16 | def compare(args): 17 | for prefix in range(0, max(sources_by_prefix.keys())): 18 | this = sources_by_prefix[prefix] 19 | that = sources_by_prefix[prefix + 1] 20 | output = f"diff_{prefix}_{prefix + 1}.diff" 21 | command = ["git", "diff", "--no-index", f"--output={output}", this, that] 22 | try: subprocess.check_output(command, cwd=sources_dir) 23 | except: pass 24 | 25 | #================================================= 26 | # Patch 27 | #================================================= 28 | 29 | def patch(args): 30 | start = sources_by_prefix[args.start] 31 | patch = subprocess.check_output(["git", "diff", start], cwd=sources_dir) 32 | for prefix in range(args.start + 1, args.end + 1): 33 | target = sources_by_prefix[prefix] 34 | apply_patch(patch, target) 35 | 36 | def apply_patch(patch, target): 37 | patcher = subprocess.Popen(["patch", "-f", target], cwd=sources_dir, stdin=subprocess.PIPE) 38 | patcher.communicate(patch) 39 | code = patcher.wait() 40 | 41 | try: os.remove(f"{target}.orig") 42 | except: pass 43 | try: os.remove(f"{target}.rej") 44 | except: pass 45 | 46 | if code != 0: 47 | sys.exit(code) 48 | 49 | #================================================= 50 | # Arguments 51 | #================================================= 52 | 53 | parser = argparse.ArgumentParser( 54 | prog="manage", 55 | description="Manages tutorial sources.", 56 | ) 57 | 58 | subparsers = parser.add_subparsers( 59 | help="command", 60 | ) 61 | 62 | #- Compare ----------------------------------------- 63 | 64 | compare_parser = subparsers.add_parser( 65 | "compare", 66 | help="Generates consecutive diffs for tutorial sources.", 67 | ) 68 | 69 | compare_parser.set_defaults(command=compare) 70 | 71 | #- Patch ----------------------------------------- 72 | 73 | patch_parser = subparsers.add_parser( 74 | "patch", 75 | help="Applies a change to a sequence of tutorial sources.", 76 | epilog=""" 77 | USAGE: The Git patch for the unstaged changes in the starting tutorial source 78 | will be applied to the other tutorial sources in the specified sequence. 79 | """, 80 | ) 81 | 82 | patch_parser.add_argument( 83 | "start", 84 | help="The number of the starting tutorial source.", 85 | type=int, 86 | ) 87 | 88 | patch_parser.add_argument( 89 | "end", 90 | help="The number of the ending tutorial source (inclusive).", 91 | type=int, 92 | ) 93 | 94 | patch_parser.set_defaults(command=patch) 95 | 96 | #- Command --------------------------------------- 97 | 98 | args = parser.parse_args() 99 | args.command(args) 100 | -------------------------------------------------------------------------------- /src/patch.ps1: -------------------------------------------------------------------------------- 1 | if (-NOT ($args.Length -eq 3)) { 2 | Write-Host "Expected original file, patched file, and prefix number to start applying patch to." 3 | exit 4 | } 5 | 6 | $original = $args[0] 7 | $patched = $args[1] 8 | $start = [int]$args[2] 9 | 10 | & "C:\Program Files\Git\usr\bin\diff.exe" -Naur $original $patched | Out-File patch.patch 11 | 12 | Get-ChildItem . -Filter *.rs | Sort-Object | Foreach-Object { 13 | $prefix = [int]$_.Name.Split("_")[0] 14 | if ($prefix -ge $start -and $_.Name -ne $patched) { 15 | Get-Content patch.patch | & "C:\Program Files\Git\usr\bin\patch.exe" -f $_.Name 16 | Remove-Item -ErrorAction Ignore ($_.Name + ".orig") 17 | Remove-Item -ErrorAction Ignore ($_.Name + ".rej") 18 | } 19 | } 20 | 21 | Remove-Item -ErrorAction Ignore patch.patch 22 | -------------------------------------------------------------------------------- /src/patch.sh: -------------------------------------------------------------------------------- 1 | if [[ $# -ne 3 ]]; then 2 | echo "Expected original file, patched file, and prefix number to start applying patch to." 3 | exit 4 | fi 5 | 6 | ORIGINAL=$1 7 | PATCHED=$2 8 | START=$3 9 | 10 | diff -Naur $ORIGINAL $PATCHED > patch.patch 11 | 12 | for FILE in *.rs; do 13 | PREFIX=$((10#"${FILE%%_*}")) 14 | if (( $PREFIX >= $START )) && [[ $FILE != $PATCHED ]]; then 15 | patch -f $FILE < patch.patch 16 | 17 | if [[ $? -ne 0 ]]; then 18 | echo "Failed to apply ($FILE)." 19 | mv ${FILE}.orig $FILE 20 | fi 21 | 22 | rm -f ${FILE}.orig 23 | rm -f ${FILE}.rej 24 | fi 25 | done 26 | 27 | rm -f patch.patch 28 | -------------------------------------------------------------------------------- /src/patch.zsh: -------------------------------------------------------------------------------- 1 | if [[ $# -ne 3 ]]; then 2 | echo "Expected original file, patched file, and prefix number to start applying patch to." 3 | exit 4 | fi 5 | 6 | ORIGINAL=$1 7 | PATCHED=$2 8 | START=$3 9 | 10 | diff -Naur $ORIGINAL $PATCHED > patch.patch 11 | 12 | for FILE in *.rs; do 13 | PREFIX=$((10#${FILE%%_*})) 14 | if (( $PREFIX >= $START )) && [[ $FILE != $PATCHED ]]; then 15 | patch -f $FILE < patch.patch 16 | 17 | if [[ $? -ne 0 ]]; then 18 | echo "Failed to apply ($FILE)." 19 | mv ${FILE}.orig $FILE 20 | fi 21 | 22 | rm -f ${FILE}.orig 23 | rm -f ${FILE}.rej 24 | fi 25 | done 26 | 27 | rm -f patch.patch 28 | -------------------------------------------------------------------------------- /src/scripts/patch.ps1: -------------------------------------------------------------------------------- 1 | if (-NOT ($args.Length -eq 3)) { 2 | Write-Host "Expected original file, patched file, and prefix number to start applying patch to." 3 | exit 4 | } 5 | 6 | $original = $args[0] 7 | $patched = $args[1] 8 | $start = [int]$args[2] 9 | 10 | & "C:\Program Files\Git\usr\bin\diff.exe" -Naur $original $patched | Out-File patch.patch 11 | 12 | Get-ChildItem . -Filter *.rs | Sort-Object | Foreach-Object { 13 | $prefix = [int]$_.Name.Split("_")[0] 14 | if ($prefix -ge $start -and $_.Name -ne $patched) { 15 | Get-Content patch.patch | & "C:\Program Files\Git\usr\bin\patch.exe" -f $_.Name 16 | Remove-Item -ErrorAction Ignore ($_.Name + ".orig") 17 | Remove-Item -ErrorAction Ignore ($_.Name + ".rej") 18 | } 19 | } 20 | 21 | Remove-Item -ErrorAction Ignore patch.patch 22 | -------------------------------------------------------------------------------- /src/scripts/patch.sh: -------------------------------------------------------------------------------- 1 | if [[ $# -ne 3 ]]; then 2 | echo "Expected original file, patched file, and prefix number to start applying patch to." 3 | exit 4 | fi 5 | 6 | ORIGINAL=$1 7 | PATCHED=$2 8 | START=$3 9 | 10 | diff -Naur $ORIGINAL $PATCHED > patch.patch 11 | 12 | for FILE in *.rs; do 13 | PREFIX=$((10#"${FILE%%_*}")) 14 | if (( $PREFIX >= $START )) && [[ $FILE != $PATCHED ]]; then 15 | patch -f $FILE < patch.patch 16 | 17 | if [[ $? -ne 0 ]]; then 18 | echo "Failed to apply ($FILE)." 19 | mv ${FILE}.orig $FILE 20 | fi 21 | 22 | rm -f ${FILE}.orig 23 | rm -f ${FILE}.rej 24 | fi 25 | done 26 | 27 | rm -f patch.patch 28 | -------------------------------------------------------------------------------- /src/scripts/patch.zsh: -------------------------------------------------------------------------------- 1 | if [[ $# -ne 3 ]]; then 2 | echo "Expected original file, patched file, and prefix number to start applying patch to." 3 | exit 4 | fi 5 | 6 | ORIGINAL=$1 7 | PATCHED=$2 8 | START=$3 9 | 10 | diff -Naur $ORIGINAL $PATCHED > patch.patch 11 | 12 | for FILE in *.rs; do 13 | PREFIX=$((10#${FILE%%_*})) 14 | if (( $PREFIX >= $START )) && [[ $FILE != $PATCHED ]]; then 15 | patch -f $FILE < patch.patch 16 | 17 | if [[ $? -ne 0 ]]; then 18 | echo "Failed to apply ($FILE)." 19 | mv ${FILE}.orig $FILE 20 | fi 21 | 22 | rm -f ${FILE}.orig 23 | rm -f ${FILE}.rej 24 | fi 25 | done 26 | 27 | rm -f patch.patch 28 | --------------------------------------------------------------------------------