├── assets ├── sky.png ├── teapot_mesh.bin └── floral_teapot.png ├── .gitignore ├── doc ├── shaders │ ├── part-1.vert.adoc │ ├── part-1.frag.adoc │ ├── part-2.frag.adoc │ ├── part-3.frag.adoc │ ├── part-2.vert.adoc │ └── part-3.vert.adoc ├── toml │ └── Cargo.adoc ├── rs │ ├── part-1-triangle.adoc │ ├── part-2-push-constants.adoc │ └── part-3-vertex-buffers.adoc ├── part-2-push-constants.adoc └── part-3-vertex-buffers.adoc ├── run_all ├── src └── bin │ ├── shaders │ ├── part-1.frag │ ├── part-2.frag │ ├── part-3.frag │ ├── part-1.vert │ ├── part-3.vert │ └── part-2.vert │ ├── part-2-push-constants.rs │ ├── part-3-vertex-buffers.rs │ └── part-1-triangle.rs ├── .travis.yml ├── run_tests ├── generate_sources ├── Cargo.toml ├── README.md ├── DEV.md └── Cargo.lock /assets/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistodon/gfx-hal-tutorials/HEAD/assets/sky.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | assets/gen/* 2 | /target 3 | ci/gfx-master/target 4 | **/*.rs.bk 5 | /src/bin/scratch.rs 6 | -------------------------------------------------------------------------------- /assets/teapot_mesh.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistodon/gfx-hal-tutorials/HEAD/assets/teapot_mesh.bin -------------------------------------------------------------------------------- /assets/floral_teapot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistodon/gfx-hal-tutorials/HEAD/assets/floral_teapot.png -------------------------------------------------------------------------------- /doc/shaders/part-1.vert.adoc: -------------------------------------------------------------------------------- 1 | :doctype: inline 2 | :outfilesuffix: 3 | ++++ 4 | include::../part-1-triangle.adoc[tag=vertex_shader] 5 | ++++ 6 | -------------------------------------------------------------------------------- /doc/toml/Cargo.adoc: -------------------------------------------------------------------------------- 1 | :doctype: inline 2 | :outfilesuffix: .toml 3 | 4 | ++++ 5 | include::../part-1-triangle.adoc[tag=cargo_toml] 6 | ++++ 7 | -------------------------------------------------------------------------------- /doc/shaders/part-1.frag.adoc: -------------------------------------------------------------------------------- 1 | :doctype: inline 2 | :outfilesuffix: 3 | ++++ 4 | include::../part-1-triangle.adoc[tag=fragment_shader] 5 | ++++ 6 | -------------------------------------------------------------------------------- /doc/shaders/part-2.frag.adoc: -------------------------------------------------------------------------------- 1 | :doctype: inline 2 | :outfilesuffix: 3 | ++++ 4 | include::../part-2-push-constants.adoc[tag=fragment_shader] 5 | ++++ 6 | -------------------------------------------------------------------------------- /doc/shaders/part-3.frag.adoc: -------------------------------------------------------------------------------- 1 | :doctype: inline 2 | :outfilesuffix: 3 | ++++ 4 | include::../part-3-vertex-buffers.adoc[tag=fragment_shader] 5 | ++++ 6 | -------------------------------------------------------------------------------- /doc/shaders/part-2.vert.adoc: -------------------------------------------------------------------------------- 1 | :doctype: inline 2 | :outfilesuffix: 3 | ++++ 4 | include::../part-2-push-constants.adoc[tag=vertex_shader] 5 | ++++ 6 | 7 | -------------------------------------------------------------------------------- /doc/shaders/part-3.vert.adoc: -------------------------------------------------------------------------------- 1 | :doctype: inline 2 | :outfilesuffix: 3 | ++++ 4 | include::../part-3-vertex-buffers.adoc[tag=vertex_shader] 5 | ++++ 6 | 7 | -------------------------------------------------------------------------------- /run_all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cargo build --bins 6 | (cd src/bin && for filename in $(ls *.rs); do ../../target/debug/${filename%.*}; done) 7 | -------------------------------------------------------------------------------- /src/bin/shaders/part-1.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0) out vec4 fragment_color; 5 | 6 | void main() { 7 | fragment_color = vec4(0.5, 0.5, 1.0, 1.0); 8 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | matrix: 4 | include: 5 | - rust: stable 6 | before_script: 7 | - sudo apt-get -y install asciidoctor 8 | - rustup component add rustfmt 9 | script: ./run_tests 10 | -------------------------------------------------------------------------------- /run_tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ -n "$1" ]]; then 6 | ./generate_sources 7 | cargo check --bin $1 8 | rustfmt src/bin/$1.rs --check 9 | else 10 | ./generate_sources 11 | cargo check --bins 12 | cargo fmt -- --check 13 | fi 14 | -------------------------------------------------------------------------------- /src/bin/shaders/part-2.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0) in vec4 vertex_color; 5 | 6 | layout(location = 0) out vec4 fragment_color; 7 | 8 | void main() { 9 | fragment_color = vertex_color; 10 | } -------------------------------------------------------------------------------- /src/bin/shaders/part-3.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0) in vec4 vertex_color; 5 | 6 | layout(location = 0) out vec4 fragment_color; 7 | 8 | void main() { 9 | fragment_color = vertex_color; 10 | } -------------------------------------------------------------------------------- /generate_sources: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | asciidoctor --destination-dir . doc/toml/Cargo.adoc 6 | asciidoctor --destination-dir src/bin doc/rs/*.adoc 7 | asciidoctor --destination-dir src/bin/shaders doc/shaders/*.adoc 8 | for rs_file in $(ls src/bin/part*.rs); do echo >> $rs_file; done 9 | -------------------------------------------------------------------------------- /src/bin/shaders/part-1.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | vec2 positions[3] = vec2[]( 5 | vec2(0.0, -0.5), 6 | vec2(-0.5, 0.5), 7 | vec2(0.5, 0.5) 8 | ); 9 | 10 | void main() { 11 | vec2 pos = positions[gl_VertexIndex]; 12 | gl_Position = vec4(pos, 0.0, 1.0); 13 | } -------------------------------------------------------------------------------- /src/bin/shaders/part-3.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0) in vec3 position; 5 | layout(location = 1) in vec3 normal; 6 | 7 | layout(push_constant) uniform PushConstants { 8 | mat4 transform; 9 | } push_constants; 10 | 11 | layout(location = 0) out vec4 vertex_color; 12 | 13 | void main() { 14 | vertex_color = vec4(abs(normal), 1.0); 15 | gl_Position = push_constants.transform * vec4(position, 1.0); 16 | } -------------------------------------------------------------------------------- /src/bin/shaders/part-2.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(push_constant) uniform PushConstants { 5 | vec4 color; 6 | vec2 pos; 7 | vec2 scale; 8 | } push_constants; 9 | 10 | layout(location = 0) out vec4 vertex_color; 11 | 12 | vec2 positions[3] = vec2[]( 13 | vec2(0.0, -0.5), 14 | vec2(-0.5, 0.5), 15 | vec2(0.5, 0.5) 16 | ); 17 | 18 | void main() { 19 | vec2 pos = positions[gl_VertexIndex] * push_constants.scale; 20 | vertex_color = push_constants.color; 21 | gl_Position = vec4((pos + push_constants.pos), 0.0, 1.0); 22 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gfx-hal-tutorials" 3 | version = "0.1.0" 4 | authors = ["Vi "] 5 | edition = "2018" 6 | license = "CC0-1.0" 7 | 8 | [dependencies] 9 | bincode = "~1.3.1" 10 | gfx-hal = "=0.6.0" 11 | shaderc = "=0.6.2" 12 | image = "~0.23.9" 13 | serde = { version = "~1.0.115", features = ["derive"] } 14 | winit = "~0.20.0" 15 | 16 | [target.'cfg(target_os = "macos")'.dependencies.backend] 17 | package = "gfx-backend-metal" 18 | version = "=0.6.2" 19 | 20 | [target.'cfg(windows)'.dependencies.backend] 21 | package = "gfx-backend-dx12" 22 | version = "=0.6.3" 23 | 24 | [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies.backend] 25 | package = "gfx-backend-vulkan" 26 | version = "=0.6.1" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gfx-hal tutorials 2 | 3 | [![Build Status](https://travis-ci.org/mistodon/gfx-hal-tutorials.svg?branch=master)](https://travis-ci.org/mistodon/gfx-hal-tutorials) 4 | 5 | This repo contains the code (and source for the write-ups) of my _Intro to gfx-hal_ tutorials: 6 | 7 | - [Part 1: Drawing a triangle](https://www.falseidolfactory.com/2020/04/01/intro-to-gfx-hal-part-1-drawing-a-triangle.html) 8 | - [Part 2: Push constants](https://www.falseidolfactory.com/2020/04/01/intro-to-gfx-hal-part-2-push-constants.html) 9 | - [Part 3: Vertex buffers](https://www.falseidolfactory.com/2020/04/16/intro-to-gfx-hal-part-3-vertex-buffers.html) 10 | 11 | ## License 12 | 13 | The _code_ for these tutorials (e.g. everything under the `src/` directory) is under the [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/) waiver. It's in the public domain, as much as it can be. Do what you like with it! 14 | 15 | The _text_ for the tutorials (e.g. everything under the `doc/` directory) is under the [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/) license. Free to use and modify as long as attribution is given. 16 | 17 | ## Contributing 18 | 19 | All contributions are welcome. If it's a very significant change, it's probably best to open an issue first so we can discuss it. Other than that, feel free to open a PR. 20 | 21 | Thanks to: 22 | 23 | - icefoxen 24 | - human9 25 | 26 | for their past contributions! 27 | -------------------------------------------------------------------------------- /doc/rs/part-1-triangle.adoc: -------------------------------------------------------------------------------- 1 | :doctype: inline 2 | :outfilesuffix: .rs 3 | :sourcepart: 1 4 | 5 | ++++ 6 | include::../part-1-triangle.adoc[tag=main_start] 7 | include::../part-1-triangle.adoc[tag=app_name] 8 | include::../part-1-triangle.adoc[tag=main_post_name] 9 | 10 | include::../part-1-triangle.adoc[tag=window_size] 11 | 12 | include::../part-1-triangle.adoc[tag=surface_extent] 13 | 14 | include::../part-1-triangle.adoc[tag=window] 15 | 16 | include::../part-1-triangle.adoc[tag=instance] 17 | 18 | include::../part-1-triangle.adoc[tag=device] 19 | 20 | include::../part-1-triangle.adoc[tag=command_pool] 21 | 22 | include::../part-1-triangle.adoc[tag=surface_color_format] 23 | 24 | include::../part-1-triangle.adoc[tag=render_pass] 25 | 26 | include::../part-1-triangle.adoc[tag=pipeline_layout] 27 | 28 | include::../part-1-triangle.adoc[tag=shaders] 29 | 30 | include::../part-1-triangle.adoc[tag=compile_shader] 31 | 32 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_start] 33 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_shader_entries] 34 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_prim] 35 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_desc] 36 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_create] 37 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_end] 38 | 39 | include::../part-1-triangle.adoc[tag=make_pipeline] 40 | 41 | include::../part-1-triangle.adoc[tag=concurrency_primitives] 42 | 43 | include::../part-1-triangle.adoc[tag=resources_struct_start] 44 | include::../part-1-triangle.adoc[tag=resources_struct_end] 45 | 46 | include::../part-1-triangle.adoc[tag=resource_holder_struct_start] 47 | include::../part-1-triangle.adoc[tag=resource_holder_struct_mid] 48 | include::../part-1-triangle.adoc[tag=resource_holder_struct_end] 49 | 50 | include::../part-1-triangle.adoc[tag=resources_start] 51 | include::../part-1-triangle.adoc[tag=resources_end] 52 | 53 | include::../part-1-triangle.adoc[tag=event_loop_start] 54 | include::../part-1-triangle.adoc[tag=rendering_prep] 55 | 56 | include::../part-1-triangle.adoc[tag=fences] 57 | 58 | include::../part-1-triangle.adoc[tag=rebuild_swapchain_start] 59 | include::../part-1-triangle.adoc[tag=rebuild_swapchain_configure] 60 | include::../part-1-triangle.adoc[tag=rebuild_swapchain_end] 61 | 62 | include::../part-1-triangle.adoc[tag=acquire_image] 63 | 64 | include::../part-1-triangle.adoc[tag=framebuffer] 65 | 66 | include::../part-1-triangle.adoc[tag=create_viewport] 67 | 68 | include::../part-1-triangle.adoc[tag=commands_start] 69 | include::../part-1-triangle.adoc[tag=commands_initial_binds] 70 | 71 | include::../part-1-triangle.adoc[tag=begin_render_pass] 72 | 73 | include::../part-1-triangle.adoc[tag=commands_bind_pipeline] 74 | 75 | include::../part-1-triangle.adoc[tag=draw_call] 76 | include::../part-1-triangle.adoc[tag=commands_end] 77 | 78 | include::../part-1-triangle.adoc[tag=submit] 79 | 80 | include::../part-1-triangle.adoc[tag=present] 81 | include::../part-1-triangle.adoc[tag=event_loop_end] 82 | } 83 | ++++ 84 | -------------------------------------------------------------------------------- /doc/rs/part-2-push-constants.adoc: -------------------------------------------------------------------------------- 1 | :doctype: inline 2 | :outfilesuffix: .rs 3 | :sourcepart: 2 4 | 5 | ++++ 6 | include::../part-2-push-constants.adoc[tag=push_constants_struct] 7 | 8 | include::../part-1-triangle.adoc[tag=main_start] 9 | include::../part-2-push-constants.adoc[tag=app_name] 10 | include::../part-1-triangle.adoc[tag=main_post_name] 11 | 12 | include::../part-1-triangle.adoc[tag=window_size] 13 | 14 | include::../part-1-triangle.adoc[tag=surface_extent] 15 | 16 | include::../part-1-triangle.adoc[tag=window] 17 | 18 | include::../part-1-triangle.adoc[tag=instance] 19 | 20 | include::../part-1-triangle.adoc[tag=device] 21 | 22 | include::../part-1-triangle.adoc[tag=command_pool] 23 | 24 | include::../part-1-triangle.adoc[tag=surface_color_format] 25 | 26 | include::../part-1-triangle.adoc[tag=render_pass] 27 | 28 | include::../part-2-push-constants.adoc[tag=pipeline_layout] 29 | 30 | include::../part-2-push-constants.adoc[tag=shaders] 31 | 32 | include::../part-1-triangle.adoc[tag=compile_shader] 33 | 34 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_start] 35 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_shader_entries] 36 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_prim] 37 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_desc] 38 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_create] 39 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_end] 40 | 41 | include::../part-1-triangle.adoc[tag=make_pipeline] 42 | 43 | include::../part-1-triangle.adoc[tag=concurrency_primitives] 44 | 45 | include::../part-1-triangle.adoc[tag=resources_struct_start] 46 | include::../part-1-triangle.adoc[tag=resources_struct_end] 47 | 48 | include::../part-1-triangle.adoc[tag=resource_holder_struct_start] 49 | include::../part-1-triangle.adoc[tag=resource_holder_struct_mid] 50 | include::../part-1-triangle.adoc[tag=resource_holder_struct_end] 51 | 52 | include::../part-1-triangle.adoc[tag=resources_start] 53 | include::../part-1-triangle.adoc[tag=resources_end] 54 | 55 | include::../part-2-push-constants.adoc[tag=start_time] 56 | 57 | include::../part-1-triangle.adoc[tag=event_loop_start] 58 | include::../part-2-push-constants.adoc[tag=rendering_prep] 59 | 60 | include::../part-1-triangle.adoc[tag=fences] 61 | 62 | include::../part-1-triangle.adoc[tag=rebuild_swapchain_start] 63 | include::../part-1-triangle.adoc[tag=rebuild_swapchain_configure] 64 | include::../part-1-triangle.adoc[tag=rebuild_swapchain_end] 65 | 66 | include::../part-1-triangle.adoc[tag=acquire_image] 67 | 68 | include::../part-1-triangle.adoc[tag=framebuffer] 69 | 70 | include::../part-1-triangle.adoc[tag=create_viewport] 71 | 72 | include::../part-2-push-constants.adoc[tag=push_constant_data] 73 | 74 | include::../part-2-push-constants.adoc[tag=push_constant_bytes_fn] 75 | 76 | include::../part-1-triangle.adoc[tag=commands_start] 77 | include::../part-1-triangle.adoc[tag=commands_initial_binds] 78 | 79 | include::../part-1-triangle.adoc[tag=begin_render_pass] 80 | 81 | include::../part-1-triangle.adoc[tag=commands_bind_pipeline] 82 | 83 | include::../part-2-push-constants.adoc[tag=draw_call] 84 | 85 | include::../part-1-triangle.adoc[tag=commands_end] 86 | 87 | include::../part-1-triangle.adoc[tag=submit] 88 | 89 | include::../part-1-triangle.adoc[tag=present] 90 | include::../part-1-triangle.adoc[tag=event_loop_end] 91 | } 92 | ++++ 93 | -------------------------------------------------------------------------------- /DEV.md: -------------------------------------------------------------------------------- 1 | Each part checklist 2 | === 3 | - [ ] Are imports sensible? 4 | - [ ] Are the shaders near the top? 5 | - [ ] Does each shader have a filename comment? 6 | - [ ] Is every code block introduced with a colon? 7 | - [ ] Does the final code file have appropriate line spacing? 8 | - [ ] Are there any unexplained unwraps/expects? 9 | - [ ] Are there any unnecessary // ... comments? 10 | 11 | Specific TODOs 12 | === 13 | 14 | ## Imminent 15 | - [ ] Fix up imports to avoid long type paths in parts 1 through 4 16 | 17 | ## Eventually 18 | - [x] Use ManuallyDrop! It's stable now! 19 | - [ ] Rename all `skybox` to `sky` 20 | - [ ] Rename all `rtt` to something more descriptive 21 | - [ ] Rename all `postprocess` to `toon` 22 | - [ ] Add a comment to each shader block showing the filename 23 | 24 | Plan 25 | === 26 | 27 | - Part 7: Indexed drawing 28 | - Draw a skybox using an index buffer 29 | - "while we're at it" load a sky texture into another desc set 30 | - Conclusion: 31 | - Looks nice and now we know how to draw multiple kinds of thing 32 | - But the skybox shouldn't be lit! We need to use a different shader 33 | - But how?? Find out next time! 34 | - Part 8: Multiple pipelines 35 | - Have a nice new shader for the skybox 36 | - Might involve a new desc set layout? 37 | - Make a new pipeline for it 38 | - Make the skybox clear the screen, but the other objects not 39 | - Conclusion: 40 | - Definitely looks nicer, but I'm getting vertigo! 41 | - Some ground would really make this place more grounded. 42 | - That doesn't sound too interesting in and of itself, but 43 | - there's some interesting stuff you can do with ground 44 | - Part 9: Stencil buffer 45 | - OK this is getting a little contrived, but 46 | - Draw a box for the ground that occludes part of the teapot 47 | - But use the stencil buffer to draw a silhouette of the teapot through 48 | - Conclusion: 49 | - Now that I can see half a spinning teapot with x-ray vision, 50 | - I'm really starting to feel at home. 51 | - But occluding the teapot has got me thinking about things I can't see 52 | - I wonder if we could expand our view of the scene somehow, without 53 | - having to write any complicated code to move a camera, of course. 54 | - Just something to reflect on for next time! 55 | - Part 10: Render-to-texture 56 | - Like many great astronauts, I want to see the dark side of this teapot. 57 | - But I already have my viewport set up real nice an simple, and I don't 58 | - want to stare right into the light 59 | - So let's add a mirror behind the teapot and get the best of both worlds! 60 | - Conclusion: 61 | - Hope you liked this contrived example. 62 | - I think this will be the end, for now. 63 | - Call it Season 1 of these tutorials. 64 | - I have a few ideas for more, but they'll have to wait. 65 | - Things I learned: 66 | 1. Never make two of something if one will do: 67 | - Don't make two textures if you can atlas them together 68 | - Don't make two descriptor sets if you can just use different Push Cs 69 | - Don't make two vertex buffers if you can put your meshes into one 70 | - Don't write two shaders if you can tweak the inputs of one 71 | - Don't make two pipelines if one will cover it 72 | 73 | Future topics 74 | === 75 | 76 | 1. Indexed drawing 77 | 2. Instanced drawing 78 | 3. Multiple render (sub-)passes 79 | 4. Multiple vertex buffers 80 | 5. Multiple vertex buffer types 81 | 6. Multiple descriptor sets 82 | 7. Multiple pipelines 83 | 8. Multiple textures/texture array 84 | 9. Cube maps 85 | 10. Compute pipelines? 86 | 11. OpenGL support 87 | 12. WebGL/WASM support 88 | 89 | # Handy dandy links: 90 | 1. Don't write descriptor sets in a command buffer or you'll get a race condition: 91 | - https://community.khronos.org/t/what-does-vkupdatedescriptorsets-exactly-do/7184 92 | -------------------------------------------------------------------------------- /doc/rs/part-3-vertex-buffers.adoc: -------------------------------------------------------------------------------- 1 | :doctype: inline 2 | :outfilesuffix: .rs 3 | :sourcepart: 3 4 | 5 | ++++ 6 | include::../part-3-vertex-buffers.adoc[tag=push_constants_struct] 7 | 8 | include::../part-3-vertex-buffers.adoc[tag=vertex_struct] 9 | 10 | include::../part-1-triangle.adoc[tag=main_start] 11 | include::../part-3-vertex-buffers.adoc[tag=app_name] 12 | include::../part-1-triangle.adoc[tag=main_post_name] 13 | 14 | include::../part-1-triangle.adoc[tag=window_size] 15 | 16 | include::../part-1-triangle.adoc[tag=surface_extent] 17 | 18 | include::../part-1-triangle.adoc[tag=window] 19 | 20 | include::../part-1-triangle.adoc[tag=instance] 21 | 22 | include::../part-1-triangle.adoc[tag=device] 23 | 24 | include::../part-1-triangle.adoc[tag=command_pool] 25 | 26 | include::../part-1-triangle.adoc[tag=surface_color_format] 27 | 28 | include::../part-3-vertex-buffers.adoc[tag=mesh_deserialize] 29 | 30 | include::../part-3-vertex-buffers.adoc[tag=make_buffer_fn_start] 31 | include::../part-3-vertex-buffers.adoc[tag=make_buffer_fn_body] 32 | include::../part-3-vertex-buffers.adoc[tag=make_buffer_fn_end] 33 | 34 | include::../part-3-vertex-buffers.adoc[tag=make_vertex_buffer] 35 | 36 | include::../part-3-vertex-buffers.adoc[tag=vertex_buffer_upload] 37 | 38 | include::../part-1-triangle.adoc[tag=render_pass] 39 | 40 | include::../part-2-push-constants.adoc[tag=pipeline_layout] 41 | 42 | include::../part-3-vertex-buffers.adoc[tag=shaders] 43 | 44 | include::../part-1-triangle.adoc[tag=compile_shader] 45 | 46 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_start] 47 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_shader_entries] 48 | include::../part-3-vertex-buffers.adoc[tag=make_pipeline_fn_prim] 49 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_desc] 50 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_create] 51 | include::../part-1-triangle.adoc[tag=make_pipeline_fn_end] 52 | 53 | include::../part-1-triangle.adoc[tag=make_pipeline] 54 | 55 | include::../part-1-triangle.adoc[tag=concurrency_primitives] 56 | 57 | include::../part-1-triangle.adoc[tag=resources_struct_start] 58 | include::../part-3-vertex-buffers.adoc[tag=resources_struct_vertex_buffer] 59 | include::../part-1-triangle.adoc[tag=resources_struct_end] 60 | 61 | include::../part-1-triangle.adoc[tag=resource_holder_struct_start] 62 | include::../part-3-vertex-buffers.adoc[tag=resource_holder_struct_vertex_buffer_take] 63 | include::../part-1-triangle.adoc[tag=resource_holder_struct_mid] 64 | include::../part-3-vertex-buffers.adoc[tag=resource_holder_struct_vertex_buffer_destroy] 65 | include::../part-1-triangle.adoc[tag=resource_holder_struct_end] 66 | 67 | include::../part-1-triangle.adoc[tag=resources_start] 68 | include::../part-3-vertex-buffers.adoc[tag=resources_vertex_buffer] 69 | include::../part-1-triangle.adoc[tag=resources_end] 70 | 71 | include::../part-2-push-constants.adoc[tag=start_time] 72 | 73 | include::../part-1-triangle.adoc[tag=event_loop_start] 74 | include::../part-2-push-constants.adoc[tag=rendering_prep] 75 | 76 | include::../part-1-triangle.adoc[tag=fences] 77 | 78 | include::../part-1-triangle.adoc[tag=rebuild_swapchain_start] 79 | include::../part-1-triangle.adoc[tag=rebuild_swapchain_configure] 80 | include::../part-1-triangle.adoc[tag=rebuild_swapchain_end] 81 | 82 | include::../part-1-triangle.adoc[tag=acquire_image] 83 | 84 | include::../part-1-triangle.adoc[tag=framebuffer] 85 | 86 | include::../part-1-triangle.adoc[tag=create_viewport] 87 | 88 | include::../part-3-vertex-buffers.adoc[tag=matrix_helper] 89 | 90 | include::../part-3-vertex-buffers.adoc[tag=push_constant_data] 91 | 92 | include::../part-2-push-constants.adoc[tag=push_constant_bytes_fn] 93 | 94 | include::../part-1-triangle.adoc[tag=commands_start] 95 | include::../part-1-triangle.adoc[tag=commands_initial_binds] 96 | 97 | include::../part-3-vertex-buffers.adoc[tag=bind_vertex_buffer] 98 | 99 | include::../part-1-triangle.adoc[tag=begin_render_pass] 100 | 101 | include::../part-1-triangle.adoc[tag=commands_bind_pipeline] 102 | 103 | include::../part-3-vertex-buffers.adoc[tag=draw_call] 104 | 105 | include::../part-1-triangle.adoc[tag=commands_end] 106 | 107 | include::../part-1-triangle.adoc[tag=submit] 108 | 109 | include::../part-1-triangle.adoc[tag=present] 110 | include::../part-1-triangle.adoc[tag=event_loop_end] 111 | } 112 | ++++ 113 | -------------------------------------------------------------------------------- /doc/part-2-push-constants.adoc: -------------------------------------------------------------------------------- 1 | :is_blog: 2 | 3 | [source,rust] 4 | ---- 5 | tag::app_name[] 6 | const APP_NAME: &'static str = "Part 2: Push constants"; 7 | end::app_name[] 8 | ---- 9 | 10 | Push constants allow us to dynamically change how things are drawn, even within a single command buffer. In my view, they're the simplest way to do so - they don't require any memory allocation, synchronization, or really much setup at all. 11 | 12 | The first thing to do is to update our shaders so that they can make use of push constants. We're going to be drawing triangles, just like the one from Part 1. But this time, we want to be able to vary the _color_, _position_, and _scale_ of each one: 13 | 14 | [source,glsl] 15 | ---- 16 | // shaders/part-2.vert 17 | tag::vertex_shader[] 18 | #version 450 19 | #extension GL_ARB_separate_shader_objects : enable 20 | 21 | layout(push_constant) uniform PushConstants { 22 | vec4 color; 23 | vec2 pos; 24 | vec2 scale; 25 | } push_constants; 26 | 27 | layout(location = 0) out vec4 vertex_color; 28 | 29 | vec2 positions[3] = vec2[]( 30 | vec2(0.0, -0.5), 31 | vec2(-0.5, 0.5), 32 | vec2(0.5, 0.5) 33 | ); 34 | 35 | void main() { 36 | vec2 pos = positions[gl_VertexIndex] * push_constants.scale; 37 | vertex_color = push_constants.color; 38 | gl_Position = vec4((pos + push_constants.pos), 0.0, 1.0); 39 | } 40 | end::vertex_shader[] 41 | ---- 42 | 43 | You can see we included a struct of the three properties we want to change. The special `layout(push_constant)` tells the shader to look in the command buffer's push constants for that data. 44 | 45 | We also added a `vertex_color` output, which the fragment shader will just pass straight through: 46 | 47 | [source,glsl] 48 | ---- 49 | // shaders/part-2.frag 50 | tag::fragment_shader[] 51 | #version 450 52 | #extension GL_ARB_separate_shader_objects : enable 53 | 54 | layout(location = 0) in vec4 vertex_color; 55 | 56 | layout(location = 0) out vec4 fragment_color; 57 | 58 | void main() { 59 | fragment_color = vertex_color; 60 | } 61 | end::fragment_shader[] 62 | ---- 63 | 64 | Last thing to do here is to make sure our pipeline is loading the new shaders. Change the filenames of the shaders where we create the pipeline: 65 | 66 | [source,rust] 67 | ---- 68 | // ... 69 | 70 | tag::shaders[] 71 | let vertex_shader = include_str!("shaders/part-2.vert"); 72 | let fragment_shader = include_str!("shaders/part-2.frag"); 73 | end::shaders[] 74 | 75 | // ... 76 | ---- 77 | 78 | Now we've seen how to use the push constant data within a shader, but that doesn't really explain what they _are_. In essence, push constants are just a small number of bytes (at least 128) that you can do whatever you like with. 79 | 80 | When we defined the `PushConstants` struct in our shader, we were just asking it to interpret some of those bytes as a struct. If we do the same in our Rust code, we make it easier to send that data: 81 | 82 | [source,rust] 83 | ---- 84 | tag::push_constants_struct[] 85 | ifeval::[{sourcepart} == 2] 86 | /// A struct representing the data that we want to supply in push constants. 87 | /// 88 | /// The `repr(C)` attribute is required to ensure that the memory layout is 89 | /// what we expect. Without it, no specific layout is guaranteed. 90 | endif::[] 91 | #[repr(C)] 92 | #[derive(Debug, Clone, Copy)] 93 | struct PushConstants { 94 | color: [f32; 4], 95 | pos: [f32; 2], 96 | scale: [f32; 2], 97 | } 98 | end::push_constants_struct[] 99 | ---- 100 | 101 | Note the `repr\(C)` attribute which tells the compiler to lay out this struct in memory the way C would. This is also (close to) how structs are laid out in shader code. (There are some awkward exceptions, but not in these tutorials.) By ensuring the layouts are the same, we can easily copy the Rust struct straight into push constants without worrying about individual fields. 102 | 103 | Next we have to make a small change to our pipeline layout to enable push constants: 104 | 105 | [source,rust] 106 | ---- 107 | tag::pipeline_layout[] 108 | let pipeline_layout = unsafe { 109 | use gfx_hal::pso::ShaderStageFlags; 110 | 111 | let push_constant_bytes = std::mem::size_of::() as u32; 112 | 113 | ifeval::[{sourcepart} == 2] 114 | // The second slice passed here defines the ranges of push constants 115 | // available to each shader stage. In this example, we're going to give 116 | // one `PushConstants` struct worth of bytes to the vertex shader. 117 | // 118 | // Out data _could_ be offset, which is why we pass a range of bytes, 119 | // but here we can start at zero since there's no data before our 120 | // struct. 121 | endif::[] 122 | device 123 | .create_pipeline_layout(&[], &[(ShaderStageFlags::VERTEX, 0..push_constant_bytes)]) 124 | .expect("Out of memory") 125 | }; 126 | end::pipeline_layout[] 127 | ---- 128 | 129 | For each draw call, we're going to supply one of our `PushConstants` structs. However, the GPU doesn't know anything about structs - all it knows is that we're going to give it some bytes. What it wants to know is _which_ of those bytes it should use for each specific shader stage. In our case, we only care about the vertex shader, and the number of bytes is however big the struct is. 130 | 131 | As a final touch before our main loop, let's also give ourselves a time parameter, so we can animate things. 132 | 133 | [source,rust] 134 | ---- 135 | tag::start_time[] 136 | ifeval::[{sourcepart} == 2] 137 | // We'll use the elapsed time to drive some animations later on. 138 | endif::[] 139 | ifdef::is_blog[] 140 | // We'll use the elapsed time to drive some animations later on. 141 | endif::is_blog[] 142 | let start_time = std::time::Instant::now(); 143 | end::start_time[] 144 | 145 | event_loop.run(move |event, _, control_flow| { 146 | // ... 147 | ---- 148 | 149 | Now we can move on to the render loop. You'll see later that we're going to need to use the `pipeline_layout`, so let's take a reference to it at the start of the loop: 150 | 151 | [source,rust] 152 | ---- 153 | Event::RedrawRequested(_) => { 154 | tag::rendering_prep[] 155 | let res: &mut Resources<_> = &mut resource_holder.0; 156 | let render_pass = &res.render_passes[0]; 157 | let pipeline_layout = &res.pipeline_layouts[0]; 158 | let pipeline = &res.pipelines[0]; 159 | end::rendering_prep[] 160 | 161 | // ... 162 | ---- 163 | 164 | Next we'll create some structs representing each triangle we want to draw. We can vary the position, color, and scale of each one, and we can use `start_time.elapsed()` to vary some of those properties over time - allowing us to animate them: 165 | 166 | [source,rust] 167 | ---- 168 | tag::push_constant_data[] 169 | // This `anim` will be a number that oscillates smoothly 170 | // between 0.0 and 1.0. 171 | let anim = start_time.elapsed().as_secs_f32().sin() * 0.5 + 0.5; 172 | 173 | let small = [0.33, 0.33]; 174 | 175 | ifeval::[{sourcepart} == 2] 176 | // Each `PushConstants` struct in this slice represents the 177 | // color, position, and scale of a triangle. This allows us to 178 | // efficiently draw the same thing multiple times with varying 179 | // parameters. 180 | endif::[] 181 | let triangles = &[ 182 | // Red triangle 183 | PushConstants { 184 | color: [1.0, 0.0, 0.0, 1.0], 185 | pos: [-0.5, -0.5], 186 | scale: small, 187 | }, 188 | // Green triangle 189 | PushConstants { 190 | color: [0.0, 1.0, 0.0, 1.0], 191 | pos: [0.0, -0.5], 192 | scale: small, 193 | }, 194 | // Blue triangle 195 | PushConstants { 196 | color: [0.0, 0.0, 1.0, 1.0], 197 | pos: [0.5, -0.5], 198 | scale: small, 199 | }, 200 | // Blue <-> cyan animated triangle 201 | PushConstants { 202 | color: [0.0, anim, 1.0, 1.0], 203 | pos: [-0.5, 0.5], 204 | scale: small, 205 | }, 206 | // Down <-> up animated triangle 207 | PushConstants { 208 | color: [1.0, 1.0, 1.0, 1.0], 209 | pos: [0.0, 0.5 - anim * 0.5], 210 | scale: small, 211 | }, 212 | // Small <-> big animated triangle 213 | PushConstants { 214 | color: [1.0, 1.0, 1.0, 1.0], 215 | pos: [0.5, 0.5], 216 | scale: [0.33 + anim * 0.33, 0.33 + anim * 0.33], 217 | }, 218 | ]; 219 | end::push_constant_data[] 220 | ---- 221 | 222 | Now before we can actually use that data to draw, we have to pass it to the GPU as a sequence of bytes. They have to be 32-bit aligned, and so gfx-hal actually requires them to be passed as a sequence of `u32` values. So, let's write a function to convert our `PushConstants` struct into that format: 223 | 224 | [source,rust] 225 | ---- 226 | tag::push_constant_bytes_fn[] 227 | /// Returns a view of a struct as a slice of `u32`s. 228 | ifndef::is_blog[] 229 | /// 230 | /// Note that this assumes the struct divides evenly into 231 | /// 4-byte chunks. If the contents are all `f32`s, which they 232 | /// often are, then this will always be the case. 233 | endif::is_blog[] 234 | unsafe fn push_constant_bytes(push_constants: &T) -> &[u32] { 235 | let size_in_bytes = std::mem::size_of::(); 236 | let size_in_u32s = size_in_bytes / std::mem::size_of::(); 237 | let start_ptr = push_constants as *const T as *const u32; 238 | std::slice::from_raw_parts(start_ptr, size_in_u32s) 239 | } 240 | end::push_constant_bytes_fn[] 241 | ---- 242 | 243 | Now finally, we can make use of our push constants to render some things. So let's replace our old draw call: 244 | 245 | [source,rust] 246 | ---- 247 | include::part-1-triangle.adoc[tag=draw_call] 248 | ---- 249 | 250 | — with something more interesting. We'll loop over the triangles we defined with `PushConstants` structs. For each one, we'll first write the push constant data to the command buffer, and then write the draw command: 251 | 252 | [source,rust] 253 | ---- 254 | tag::draw_call[] 255 | for triangle in triangles { 256 | use gfx_hal::pso::ShaderStageFlags; 257 | 258 | ifeval::[{sourcepart} == 2] 259 | // This encodes the actual push constants themselves 260 | // into the command buffer. The vertex shader will be 261 | // able to access these properties. 262 | endif::[] 263 | command_buffer.push_graphics_constants( 264 | pipeline_layout, 265 | ShaderStageFlags::VERTEX, 266 | 0, 267 | push_constant_bytes(triangle), 268 | ); 269 | 270 | command_buffer.draw(0..3, 0..1); 271 | } 272 | end::draw_call[] 273 | ---- 274 | 275 | If all is well, when you run the application, you should see six triangles like this: 276 | -------------------------------------------------------------------------------- /src/bin/part-2-push-constants.rs: -------------------------------------------------------------------------------- 1 | /// A struct representing the data that we want to supply in push constants. 2 | /// 3 | /// The `repr(C)` attribute is required to ensure that the memory layout is 4 | /// what we expect. Without it, no specific layout is guaranteed. 5 | #[repr(C)] 6 | #[derive(Debug, Clone, Copy)] 7 | struct PushConstants { 8 | color: [f32; 4], 9 | pos: [f32; 2], 10 | scale: [f32; 2], 11 | } 12 | 13 | fn main() { 14 | use std::mem::ManuallyDrop; 15 | 16 | use gfx_hal::{ 17 | device::Device, 18 | window::{Extent2D, PresentationSurface, Surface}, 19 | Instance, 20 | }; 21 | use shaderc::ShaderKind; 22 | 23 | const APP_NAME: &'static str = "Part 2: Push constants"; 24 | const WINDOW_SIZE: [u32; 2] = [512, 512]; 25 | 26 | let event_loop = winit::event_loop::EventLoop::new(); 27 | 28 | let (logical_window_size, physical_window_size) = { 29 | use winit::dpi::{LogicalSize, PhysicalSize}; 30 | 31 | let dpi = event_loop.primary_monitor().scale_factor(); 32 | let logical: LogicalSize = WINDOW_SIZE.into(); 33 | let physical: PhysicalSize = logical.to_physical(dpi); 34 | 35 | (logical, physical) 36 | }; 37 | 38 | let mut surface_extent = Extent2D { 39 | width: physical_window_size.width, 40 | height: physical_window_size.height, 41 | }; 42 | 43 | let window = winit::window::WindowBuilder::new() 44 | .with_title(APP_NAME) 45 | .with_inner_size(logical_window_size) 46 | .build(&event_loop) 47 | .expect("Failed to create window"); 48 | 49 | let (instance, surface, adapter) = { 50 | let instance = backend::Instance::create(APP_NAME, 1).expect("Backend not supported"); 51 | 52 | let surface = unsafe { 53 | instance 54 | .create_surface(&window) 55 | .expect("Failed to create surface for window") 56 | }; 57 | 58 | let adapter = instance.enumerate_adapters().remove(0); 59 | 60 | (instance, surface, adapter) 61 | }; 62 | 63 | let (device, mut queue_group) = { 64 | use gfx_hal::queue::QueueFamily; 65 | 66 | let queue_family = adapter 67 | .queue_families 68 | .iter() 69 | .find(|family| { 70 | surface.supports_queue_family(family) && family.queue_type().supports_graphics() 71 | }) 72 | .expect("No compatible queue family found"); 73 | 74 | let mut gpu = unsafe { 75 | use gfx_hal::adapter::PhysicalDevice; 76 | 77 | adapter 78 | .physical_device 79 | .open(&[(queue_family, &[1.0])], gfx_hal::Features::empty()) 80 | .expect("Failed to open device") 81 | }; 82 | 83 | (gpu.device, gpu.queue_groups.pop().unwrap()) 84 | }; 85 | 86 | let (command_pool, mut command_buffer) = unsafe { 87 | use gfx_hal::command::Level; 88 | use gfx_hal::pool::{CommandPool, CommandPoolCreateFlags}; 89 | 90 | let mut command_pool = device 91 | .create_command_pool(queue_group.family, CommandPoolCreateFlags::empty()) 92 | .expect("Out of memory"); 93 | 94 | let command_buffer = command_pool.allocate_one(Level::Primary); 95 | 96 | (command_pool, command_buffer) 97 | }; 98 | 99 | let surface_color_format = { 100 | use gfx_hal::format::{ChannelType, Format}; 101 | 102 | let supported_formats = surface 103 | .supported_formats(&adapter.physical_device) 104 | .unwrap_or(vec![]); 105 | 106 | let default_format = *supported_formats.get(0).unwrap_or(&Format::Rgba8Srgb); 107 | 108 | supported_formats 109 | .into_iter() 110 | .find(|format| format.base_format().1 == ChannelType::Srgb) 111 | .unwrap_or(default_format) 112 | }; 113 | 114 | let render_pass = { 115 | use gfx_hal::image::Layout; 116 | use gfx_hal::pass::{ 117 | Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp, SubpassDesc, 118 | }; 119 | 120 | let color_attachment = Attachment { 121 | format: Some(surface_color_format), 122 | samples: 1, 123 | ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store), 124 | stencil_ops: AttachmentOps::DONT_CARE, 125 | layouts: Layout::Undefined..Layout::Present, 126 | }; 127 | 128 | let subpass = SubpassDesc { 129 | colors: &[(0, Layout::ColorAttachmentOptimal)], 130 | depth_stencil: None, 131 | inputs: &[], 132 | resolves: &[], 133 | preserves: &[], 134 | }; 135 | 136 | unsafe { 137 | device 138 | .create_render_pass(&[color_attachment], &[subpass], &[]) 139 | .expect("Out of memory") 140 | } 141 | }; 142 | 143 | let pipeline_layout = unsafe { 144 | use gfx_hal::pso::ShaderStageFlags; 145 | 146 | let push_constant_bytes = std::mem::size_of::() as u32; 147 | 148 | // The second slice passed here defines the ranges of push constants 149 | // available to each shader stage. In this example, we're going to give 150 | // one `PushConstants` struct worth of bytes to the vertex shader. 151 | // 152 | // Out data _could_ be offset, which is why we pass a range of bytes, 153 | // but here we can start at zero since there's no data before our 154 | // struct. 155 | device 156 | .create_pipeline_layout(&[], &[(ShaderStageFlags::VERTEX, 0..push_constant_bytes)]) 157 | .expect("Out of memory") 158 | }; 159 | 160 | let vertex_shader = include_str!("shaders/part-2.vert"); 161 | let fragment_shader = include_str!("shaders/part-2.frag"); 162 | 163 | /// Compile some GLSL shader source to SPIR-V. 164 | /// 165 | /// We tend to write shaders in high-level languages, but the GPU doesn't 166 | /// work with that directly. Instead, we can convert it to an intermediate 167 | /// representation: SPIR-V. This is more easily interpreted and optimized 168 | /// by your graphics card. As an added bonus, this allows us to use the 169 | /// same shader code across different backends. 170 | fn compile_shader(glsl: &str, shader_kind: ShaderKind) -> Vec { 171 | let mut compiler = shaderc::Compiler::new().unwrap(); 172 | 173 | let compiled_shader = compiler 174 | .compile_into_spirv(glsl, shader_kind, "unnamed", "main", None) 175 | .expect("Failed to compile shader"); 176 | 177 | compiled_shader.as_binary().to_vec() 178 | } 179 | 180 | /// Create a pipeline with the given layout and shaders. 181 | /// 182 | /// A pipeline contains nearly all the required information for rendering, 183 | /// and is only usable within the render pass it's defined for. 184 | unsafe fn make_pipeline( 185 | device: &B::Device, 186 | render_pass: &B::RenderPass, 187 | pipeline_layout: &B::PipelineLayout, 188 | vertex_shader: &str, 189 | fragment_shader: &str, 190 | ) -> B::GraphicsPipeline { 191 | use gfx_hal::pass::Subpass; 192 | use gfx_hal::pso::{ 193 | BlendState, ColorBlendDesc, ColorMask, EntryPoint, Face, GraphicsPipelineDesc, 194 | InputAssemblerDesc, Primitive, PrimitiveAssemblerDesc, Rasterizer, Specialization, 195 | }; 196 | let vertex_shader_module = device 197 | .create_shader_module(&compile_shader(vertex_shader, ShaderKind::Vertex)) 198 | .expect("Failed to create vertex shader module"); 199 | 200 | let fragment_shader_module = device 201 | .create_shader_module(&compile_shader(fragment_shader, ShaderKind::Fragment)) 202 | .expect("Failed to create fragment shader module"); 203 | 204 | let (vs_entry, fs_entry) = ( 205 | EntryPoint { 206 | entry: "main", 207 | module: &vertex_shader_module, 208 | specialization: Specialization::default(), 209 | }, 210 | EntryPoint { 211 | entry: "main", 212 | module: &fragment_shader_module, 213 | specialization: Specialization::default(), 214 | }, 215 | ); 216 | let primitive_assembler = PrimitiveAssemblerDesc::Vertex { 217 | buffers: &[], 218 | attributes: &[], 219 | input_assembler: InputAssemblerDesc::new(Primitive::TriangleList), 220 | vertex: vs_entry, 221 | tessellation: None, 222 | geometry: None, 223 | }; 224 | let mut pipeline_desc = GraphicsPipelineDesc::new( 225 | primitive_assembler, 226 | Rasterizer { 227 | cull_face: Face::BACK, 228 | ..Rasterizer::FILL 229 | }, 230 | Some(fs_entry), 231 | pipeline_layout, 232 | Subpass { 233 | index: 0, 234 | main_pass: render_pass, 235 | }, 236 | ); 237 | 238 | pipeline_desc.blender.targets.push(ColorBlendDesc { 239 | mask: ColorMask::ALL, 240 | blend: Some(BlendState::ALPHA), 241 | }); 242 | let pipeline = device 243 | .create_graphics_pipeline(&pipeline_desc, None) 244 | .expect("Failed to create graphics pipeline"); 245 | 246 | device.destroy_shader_module(vertex_shader_module); 247 | device.destroy_shader_module(fragment_shader_module); 248 | 249 | pipeline 250 | }; 251 | 252 | let pipeline = unsafe { 253 | make_pipeline::( 254 | &device, 255 | &render_pass, 256 | &pipeline_layout, 257 | vertex_shader, 258 | fragment_shader, 259 | ) 260 | }; 261 | 262 | let submission_complete_fence = device.create_fence(true).expect("Out of memory"); 263 | let rendering_complete_semaphore = device.create_semaphore().expect("Out of memory"); 264 | 265 | struct Resources { 266 | instance: B::Instance, 267 | surface: B::Surface, 268 | device: B::Device, 269 | render_passes: Vec, 270 | pipeline_layouts: Vec, 271 | pipelines: Vec, 272 | command_pool: B::CommandPool, 273 | submission_complete_fence: B::Fence, 274 | rendering_complete_semaphore: B::Semaphore, 275 | } 276 | 277 | struct ResourceHolder(ManuallyDrop>); 278 | 279 | impl Drop for ResourceHolder { 280 | fn drop(&mut self) { 281 | unsafe { 282 | let Resources { 283 | instance, 284 | mut surface, 285 | device, 286 | command_pool, 287 | render_passes, 288 | pipeline_layouts, 289 | pipelines, 290 | submission_complete_fence, 291 | rendering_complete_semaphore, 292 | } = ManuallyDrop::take(&mut self.0); 293 | 294 | device.destroy_semaphore(rendering_complete_semaphore); 295 | device.destroy_fence(submission_complete_fence); 296 | for pipeline in pipelines { 297 | device.destroy_graphics_pipeline(pipeline); 298 | } 299 | for pipeline_layout in pipeline_layouts { 300 | device.destroy_pipeline_layout(pipeline_layout); 301 | } 302 | for render_pass in render_passes { 303 | device.destroy_render_pass(render_pass); 304 | } 305 | device.destroy_command_pool(command_pool); 306 | surface.unconfigure_swapchain(&device); 307 | instance.destroy_surface(surface); 308 | } 309 | } 310 | } 311 | 312 | let mut resource_holder: ResourceHolder = 313 | ResourceHolder(ManuallyDrop::new(Resources { 314 | instance, 315 | surface, 316 | device, 317 | command_pool, 318 | render_passes: vec![render_pass], 319 | pipeline_layouts: vec![pipeline_layout], 320 | pipelines: vec![pipeline], 321 | submission_complete_fence, 322 | rendering_complete_semaphore, 323 | })); 324 | 325 | // We'll use the elapsed time to drive some animations later on. 326 | let start_time = std::time::Instant::now(); 327 | 328 | let mut should_configure_swapchain = true; 329 | 330 | event_loop.run(move |event, _, control_flow| { 331 | use winit::event::{Event, WindowEvent}; 332 | use winit::event_loop::ControlFlow; 333 | 334 | match event { 335 | Event::WindowEvent { event, .. } => match event { 336 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 337 | WindowEvent::Resized(dims) => { 338 | surface_extent = Extent2D { 339 | width: dims.width, 340 | height: dims.height, 341 | }; 342 | should_configure_swapchain = true; 343 | } 344 | WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { 345 | surface_extent = Extent2D { 346 | width: new_inner_size.width, 347 | height: new_inner_size.height, 348 | }; 349 | should_configure_swapchain = true; 350 | } 351 | _ => (), 352 | }, 353 | Event::MainEventsCleared => window.request_redraw(), 354 | Event::RedrawRequested(_) => { 355 | let res: &mut Resources<_> = &mut resource_holder.0; 356 | let render_pass = &res.render_passes[0]; 357 | let pipeline_layout = &res.pipeline_layouts[0]; 358 | let pipeline = &res.pipelines[0]; 359 | 360 | unsafe { 361 | use gfx_hal::pool::CommandPool; 362 | 363 | // We refuse to wait more than a second, to avoid hanging. 364 | let render_timeout_ns = 1_000_000_000; 365 | 366 | res.device 367 | .wait_for_fence(&res.submission_complete_fence, render_timeout_ns) 368 | .expect("Out of memory or device lost"); 369 | 370 | res.device 371 | .reset_fence(&res.submission_complete_fence) 372 | .expect("Out of memory"); 373 | 374 | res.command_pool.reset(false); 375 | } 376 | 377 | if should_configure_swapchain { 378 | use gfx_hal::window::SwapchainConfig; 379 | 380 | let caps = res.surface.capabilities(&adapter.physical_device); 381 | 382 | let mut swapchain_config = 383 | SwapchainConfig::from_caps(&caps, surface_color_format, surface_extent); 384 | 385 | // This seems to fix some fullscreen slowdown on macOS. 386 | if caps.image_count.contains(&3) { 387 | swapchain_config.image_count = 3; 388 | } 389 | 390 | surface_extent = swapchain_config.extent; 391 | 392 | unsafe { 393 | res.surface 394 | .configure_swapchain(&res.device, swapchain_config) 395 | .expect("Failed to configure swapchain"); 396 | }; 397 | 398 | should_configure_swapchain = false; 399 | } 400 | 401 | let surface_image = unsafe { 402 | // We refuse to wait more than a second, to avoid hanging. 403 | let acquire_timeout_ns = 1_000_000_000; 404 | 405 | match res.surface.acquire_image(acquire_timeout_ns) { 406 | Ok((image, _)) => image, 407 | Err(_) => { 408 | should_configure_swapchain = true; 409 | return; 410 | } 411 | } 412 | }; 413 | 414 | let framebuffer = unsafe { 415 | use std::borrow::Borrow; 416 | 417 | use gfx_hal::image::Extent; 418 | 419 | res.device 420 | .create_framebuffer( 421 | render_pass, 422 | vec![surface_image.borrow()], 423 | Extent { 424 | width: surface_extent.width, 425 | height: surface_extent.height, 426 | depth: 1, 427 | }, 428 | ) 429 | .unwrap() 430 | }; 431 | 432 | let viewport = { 433 | use gfx_hal::pso::{Rect, Viewport}; 434 | 435 | Viewport { 436 | rect: Rect { 437 | x: 0, 438 | y: 0, 439 | w: surface_extent.width as i16, 440 | h: surface_extent.height as i16, 441 | }, 442 | depth: 0.0..1.0, 443 | } 444 | }; 445 | 446 | // This `anim` will be a number that oscillates smoothly 447 | // between 0.0 and 1.0. 448 | let anim = start_time.elapsed().as_secs_f32().sin() * 0.5 + 0.5; 449 | 450 | let small = [0.33, 0.33]; 451 | 452 | // Each `PushConstants` struct in this slice represents the 453 | // color, position, and scale of a triangle. This allows us to 454 | // efficiently draw the same thing multiple times with varying 455 | // parameters. 456 | let triangles = &[ 457 | // Red triangle 458 | PushConstants { 459 | color: [1.0, 0.0, 0.0, 1.0], 460 | pos: [-0.5, -0.5], 461 | scale: small, 462 | }, 463 | // Green triangle 464 | PushConstants { 465 | color: [0.0, 1.0, 0.0, 1.0], 466 | pos: [0.0, -0.5], 467 | scale: small, 468 | }, 469 | // Blue triangle 470 | PushConstants { 471 | color: [0.0, 0.0, 1.0, 1.0], 472 | pos: [0.5, -0.5], 473 | scale: small, 474 | }, 475 | // Blue <-> cyan animated triangle 476 | PushConstants { 477 | color: [0.0, anim, 1.0, 1.0], 478 | pos: [-0.5, 0.5], 479 | scale: small, 480 | }, 481 | // Down <-> up animated triangle 482 | PushConstants { 483 | color: [1.0, 1.0, 1.0, 1.0], 484 | pos: [0.0, 0.5 - anim * 0.5], 485 | scale: small, 486 | }, 487 | // Small <-> big animated triangle 488 | PushConstants { 489 | color: [1.0, 1.0, 1.0, 1.0], 490 | pos: [0.5, 0.5], 491 | scale: [0.33 + anim * 0.33, 0.33 + anim * 0.33], 492 | }, 493 | ]; 494 | 495 | /// Returns a view of a struct as a slice of `u32`s. 496 | /// 497 | /// Note that this assumes the struct divides evenly into 498 | /// 4-byte chunks. If the contents are all `f32`s, which they 499 | /// often are, then this will always be the case. 500 | unsafe fn push_constant_bytes(push_constants: &T) -> &[u32] { 501 | let size_in_bytes = std::mem::size_of::(); 502 | let size_in_u32s = size_in_bytes / std::mem::size_of::(); 503 | let start_ptr = push_constants as *const T as *const u32; 504 | std::slice::from_raw_parts(start_ptr, size_in_u32s) 505 | } 506 | 507 | unsafe { 508 | use gfx_hal::command::{ 509 | ClearColor, ClearValue, CommandBuffer, CommandBufferFlags, SubpassContents, 510 | }; 511 | 512 | command_buffer.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); 513 | 514 | command_buffer.set_viewports(0, &[viewport.clone()]); 515 | command_buffer.set_scissors(0, &[viewport.rect]); 516 | 517 | command_buffer.begin_render_pass( 518 | render_pass, 519 | &framebuffer, 520 | viewport.rect, 521 | &[ClearValue { 522 | color: ClearColor { 523 | float32: [0.0, 0.0, 0.0, 1.0], 524 | }, 525 | }], 526 | SubpassContents::Inline, 527 | ); 528 | 529 | command_buffer.bind_graphics_pipeline(pipeline); 530 | 531 | for triangle in triangles { 532 | use gfx_hal::pso::ShaderStageFlags; 533 | 534 | // This encodes the actual push constants themselves 535 | // into the command buffer. The vertex shader will be 536 | // able to access these properties. 537 | command_buffer.push_graphics_constants( 538 | pipeline_layout, 539 | ShaderStageFlags::VERTEX, 540 | 0, 541 | push_constant_bytes(triangle), 542 | ); 543 | 544 | command_buffer.draw(0..3, 0..1); 545 | } 546 | 547 | command_buffer.end_render_pass(); 548 | command_buffer.finish(); 549 | } 550 | 551 | unsafe { 552 | use gfx_hal::queue::{CommandQueue, Submission}; 553 | 554 | let submission = Submission { 555 | command_buffers: vec![&command_buffer], 556 | wait_semaphores: None, 557 | signal_semaphores: vec![&res.rendering_complete_semaphore], 558 | }; 559 | 560 | queue_group.queues[0].submit(submission, Some(&res.submission_complete_fence)); 561 | 562 | let result = queue_group.queues[0].present( 563 | &mut res.surface, 564 | surface_image, 565 | Some(&res.rendering_complete_semaphore), 566 | ); 567 | 568 | should_configure_swapchain |= result.is_err(); 569 | 570 | res.device.destroy_framebuffer(framebuffer); 571 | } 572 | } 573 | _ => (), 574 | } 575 | }); 576 | } 577 | -------------------------------------------------------------------------------- /doc/part-3-vertex-buffers.adoc: -------------------------------------------------------------------------------- 1 | :is_blog: 2 | 3 | [source,rust] 4 | ---- 5 | tag::app_name[] 6 | const APP_NAME: &'static str = "Part 3: Vertex buffers"; 7 | end::app_name[] 8 | ---- 9 | 10 | As mentioned, we're going to be drawing a teapot today. Teapots - as a rule - tend to have tens of thousands of vertices (instead of just three) so it would be a little prohibitive to try and hard-code them in our vertex shader, the way we did before. 11 | 12 | Instead what we're going to do is efficiently store those vertices in a buffer, and then pass them as _inputs_ to the shader via vertex attributes. We will naturally have a `position` input, but our teapot also contains a vertex `normal`, so we'll include that too: 13 | 14 | [source,glsl] 15 | ---- 16 | // shaders/part-3.vert 17 | tag::vertex_shader[] 18 | #version 450 19 | #extension GL_ARB_separate_shader_objects : enable 20 | 21 | layout(location = 0) in vec3 position; 22 | layout(location = 1) in vec3 normal; 23 | 24 | layout(push_constant) uniform PushConstants { 25 | mat4 transform; 26 | } push_constants; 27 | 28 | layout(location = 0) out vec4 vertex_color; 29 | 30 | void main() { 31 | vertex_color = vec4(abs(normal), 1.0); 32 | gl_Position = push_constants.transform * vec4(position, 1.0); 33 | } 34 | end::vertex_shader[] 35 | ---- 36 | 37 | A few things to note: first the `location` of the attribute, secondly that we're passing the absolute value of the `normal` as the vertex color, and lastly that we've changed our push constants to contain a single matrix. 38 | 39 | The `location` is - for our purposes - just an ID number. We'll use them later when we're updating our pipeline. As for the vertex color - this is just a nice way to visualize normals. We don't _need_ to do this, but it's more interesting than a flat color. Finally the `transform` matrix replaces the position and scale parameters we had before. Now we can apply an arbitrary 3D transformation to our vertices, which we'll use to orient the teapot. 40 | 41 | Happily, the fragment shader is unchanged from last time: 42 | 43 | [source,glsl] 44 | ---- 45 | // shaders/part-3.frag 46 | tag::fragment_shader[] 47 | #version 450 48 | #extension GL_ARB_separate_shader_objects : enable 49 | 50 | layout(location = 0) in vec4 vertex_color; 51 | 52 | layout(location = 0) out vec4 fragment_color; 53 | 54 | void main() { 55 | fragment_color = vertex_color; 56 | } 57 | end::fragment_shader[] 58 | ---- 59 | 60 | And again, we now update the filenames of the shaders we're loading: 61 | 62 | [source,rust] 63 | ---- 64 | // ... 65 | 66 | tag::shaders[] 67 | let vertex_shader = include_str!("shaders/part-3.vert"); 68 | let fragment_shader = include_str!("shaders/part-3.frag"); 69 | end::shaders[] 70 | 71 | // ... 72 | ---- 73 | 74 | Onto the Rust code. 75 | 76 | Our goal is to create a vertex buffer, but first let's find some data to put in it. In https://github.com/mistodon/gfx-hal-tutorials/tree/master/assets[the repo] is a mesh of the Utah teapot, serialized with the `bincode` crate. Really it's just a `Vec` of vertices efficiently packed into a binary file. To deserialize it, we'll need to define a compatible `Vertex` struct: 77 | 78 | [source,rust] 79 | ---- 80 | tag::vertex_struct[] 81 | ifeval::[{sourcepart} == 3] 82 | /// A struct representing a single vertex in 3D space with a normal. 83 | /// 84 | /// The `repr(C)` attribute is required to ensure that the memory layout is 85 | /// what we expect. Without it, no specific layout is guaranteed. 86 | endif::[] 87 | #[derive(serde::Deserialize)] 88 | #[repr(C)] 89 | struct Vertex { 90 | position: [f32; 3], 91 | normal: [f32; 3], 92 | } 93 | end::vertex_struct[] 94 | ---- 95 | 96 | Once we have this struct defined, we can load and deserialize the mesh: 97 | 98 | [source,rust] 99 | ---- 100 | tag::mesh_deserialize[] 101 | // The `teapot_mesh.bin` is just a `Vec` that was serialized 102 | // using the `bincode` crate. So we can deserialize it directly. 103 | let binary_mesh_data = include_bytes!("../../assets/teapot_mesh.bin"); 104 | let mesh: Vec = 105 | bincode::deserialize(binary_mesh_data).expect("Failed to deserialize mesh"); 106 | end::mesh_deserialize[] 107 | ---- 108 | 109 | (Note of course that we do this _outside_ our render loop - we don't have to do this every frame.) 110 | 111 | Now to create the buffer itself. We're going to have to make more buffers in future, so let's create a function for this. You'll thank me later, I promise: 112 | 113 | [source,rust] 114 | ---- 115 | tag::make_buffer_fn_start[] 116 | /// Create an empty buffer with the given size and properties. 117 | ifndef::is_blog[] 118 | /// 119 | /// Buffers can be used for various things. The `usage` parameter defines 120 | /// how the buffer should be treated (vertex buffer, index buffer, etc). 121 | /// The `properties` specify the kind of memory that should be used to 122 | /// store this buffer (CPU visible, device local, etc). 123 | endif::is_blog[] 124 | unsafe fn make_buffer( 125 | device: &B::Device, 126 | physical_device: &B::PhysicalDevice, 127 | buffer_len: usize, 128 | usage: gfx_hal::buffer::Usage, 129 | properties: gfx_hal::memory::Properties, 130 | ) -> (B::Memory, B::Buffer) { 131 | end::make_buffer_fn_start[] 132 | todo!() 133 | tag::make_buffer_fn_end[] 134 | } 135 | end::make_buffer_fn_end[] 136 | ---- 137 | 138 | This function is going to give us an empty buffer - but we have to specify the _kind_ of buffer we want, not just the size of it. The `usage` parameter specifies how we plan to _use_ the buffer, while the `properties` define what kind of memory to store it in (which is largely a matter of optimization). We'll see those parameters in more detail when we call the function. 139 | 140 | But before that, let's fill in the function body: 141 | 142 | [source,rust] 143 | ---- 144 | // fn make_buffer(...) { 145 | tag::make_buffer_fn_body[] 146 | use gfx_hal::{adapter::PhysicalDevice, MemoryTypeId}; 147 | 148 | ifeval::[{sourcepart} == 3] 149 | // This creates a handle to a buffer. The `buffer_len` is in bytes, 150 | // and the usage states what kind of buffer it is. For this part, 151 | // we're making a vertex buffer, so you'll see later that we pass 152 | // `Usage::VERTEX` for this parameter. 153 | endif::[] 154 | let mut buffer = device 155 | .create_buffer(buffer_len as u64, usage) 156 | .expect("Failed to create buffer"); 157 | 158 | ifeval::[{sourcepart} == 3] 159 | // The device may have its own requirements for storing a buffer of 160 | // this certain size and properties. It returns a `Requirements` struct 161 | // from which we'll use two fields: `size` and `type_mask`. 162 | // 163 | // The `size` field should be pretty straightforward - it may differ 164 | // from `buffer_len` if there are padding/alignment requirements. 165 | // 166 | // The `type_mask` is a bitmask representing which memory types are 167 | // compatible. 168 | endif::[] 169 | let req = device.get_buffer_requirements(&buffer); 170 | 171 | ifeval::[{sourcepart} == 3] 172 | // This list of `memory_type` corresponds to the list represented by 173 | // the `type_mask` above. If the nth bit in the mask is `1`, then the 174 | // nth memory type in this list is supported. 175 | endif::[] 176 | let memory_types = physical_device.memory_properties().memory_types; 177 | 178 | ifeval::[{sourcepart} == 3] 179 | // We iterate over all the memory types and select the first one that 180 | // is both supported (e.g. in the `type_mask`), and supports the 181 | // `properties` we requested. In our case this is `CPU_VISIBLE` as 182 | // we'll see later. 183 | endif::[] 184 | let memory_type = memory_types 185 | .iter() 186 | .enumerate() 187 | .find(|(id, mem_type)| { 188 | let type_supported = req.type_mask & (1_u32 << id) != 0; 189 | type_supported && mem_type.properties.contains(properties) 190 | }) 191 | .map(|(id, _ty)| MemoryTypeId(id)) 192 | .expect("No compatible memory type available"); 193 | 194 | ifeval::[{sourcepart} == 3] 195 | // Now that we know the size and type of the memory to allocate, we can 196 | // go ahead and do so. 197 | endif::[] 198 | let buffer_memory = device 199 | .allocate_memory(memory_type, req.size) 200 | .expect("Failed to allocate buffer memory"); 201 | 202 | ifeval::[{sourcepart} == 3] 203 | // Now that we have memory to back our buffer, we can bind that buffer 204 | // handle to the memory. That buffer now has some actual storage 205 | // associated with it. 206 | endif::[] 207 | device 208 | .bind_buffer_memory(&buffer_memory, 0, &mut buffer) 209 | .expect("Failed to bind buffer memory"); 210 | 211 | (buffer_memory, buffer) 212 | end::make_buffer_fn_body[] 213 | } 214 | ---- 215 | 216 | To break down the above, what we're doing is: 217 | 218 | 1. Create an opaque `buffer` object. 219 | 2. Determine a `memory_type` that's compatible with our requirements. 220 | 3. Allocate a big enough block of that memory. 221 | 4. Bind the buffer_memory to the buffer, so the buffer knows where its contents are stored. 222 | 223 | Possibly the hardest part to understand is how the `memory_type` is selected. The GPU provides different heaps of memory with different performance characteristics. In the above code, we get a list of memory types, and also a `type_mask` from our buffer. The `type_mask` is a bit mask defining whether each item in the list of memory types is suitable for this buffer. The comments in https://github.com/mistodon/gfx-hal-tutorials/blob/master/src/bin/part-3-vertex-buffers.rs#L144[the code itself] may do a better job of explaining. 224 | 225 | With the `make_buffer` function defined, we can, um, make our buffer: 226 | 227 | [source,rust] 228 | ---- 229 | tag::make_vertex_buffer[] 230 | ifeval::[{sourcepart} == 3] 231 | // The size of our vertex data is the number of vertices times 232 | // the size of one `Vertex`. 233 | endif::[] 234 | let vertex_buffer_len = mesh.len() * std::mem::size_of::(); 235 | 236 | ifeval::[{sourcepart} == 3] 237 | // We create a buffer, specifying that it should be used to store vertex 238 | // data (hence the `Usage::VERTEX`) and that it should be visible to the 239 | // CPU so we can load data into it (hence the `Properties::CPU_VISIBLE`). 240 | endif::[] 241 | let (vertex_buffer_memory, vertex_buffer) = unsafe { 242 | use gfx_hal::buffer::Usage; 243 | use gfx_hal::memory::Properties; 244 | 245 | make_buffer::( 246 | &device, 247 | &adapter.physical_device, 248 | vertex_buffer_len, 249 | Usage::VERTEX, 250 | Properties::CPU_VISIBLE, 251 | ) 252 | }; 253 | end::make_vertex_buffer[] 254 | ---- 255 | 256 | We pass parameters to say that we want a `VERTEX` buffer, and that we want it to be `CPU_VISIBLE` so that we can write to it from our CPU-side Rust code. 257 | 258 | That last parameter is important, because the buffer we just created is currently empty. The next thing we have to do is fill it with our mesh data. We already have some memory allocated for the buffer, so we just have to copy the vertex data into it: 259 | 260 | [source,rust] 261 | ---- 262 | tag::vertex_buffer_upload[] 263 | unsafe { 264 | use gfx_hal::memory::Segment; 265 | 266 | ifeval::[{sourcepart} == 3] 267 | // Mapping the buffer memory gives us a pointer directly to the 268 | // contents of the buffer, which lets us easily copy data into it. 269 | // 270 | // We pass `Segment::ALL` to say that we want to map the *whole* 271 | // buffer, as opposed to just part of it. 272 | endif::[] 273 | let mapped_memory = device 274 | .map_memory(&vertex_buffer_memory, Segment::ALL) 275 | .expect("Failed to map memory"); 276 | 277 | ifeval::[{sourcepart} == 3] 278 | // Here we just copy `vertex_buffer_len` *from* the `mesh` data 279 | // *to* to the `mapped_memory`. 280 | endif::[] 281 | std::ptr::copy_nonoverlapping(mesh.as_ptr() as *const u8, mapped_memory, vertex_buffer_len); 282 | 283 | ifeval::[{sourcepart} == 3] 284 | // Flushing the mapped memory ensures that the data we wrote to the 285 | // memory actually makes it to the graphics device. The copy alone does 286 | // not guarantee this. 287 | // 288 | // Again, we could supply multiple ranges (of multiple buffers even) 289 | // but instead we just flush `ALL` of our single buffer. 290 | endif::[] 291 | device 292 | .flush_mapped_memory_ranges(vec![(&vertex_buffer_memory, Segment::ALL)]) 293 | .expect("Out of memory"); 294 | 295 | device.unmap_memory(&vertex_buffer_memory); 296 | } 297 | end::vertex_buffer_upload[] 298 | ---- 299 | 300 | The first thing we do is map the buffer memory, which gives us a pointer to it. The benefit of that memory being CPU visible is that we can do this. Then we simply copy our deserialized mesh straight to that pointer, and flush the mapped memory to ensure it actually makes it to the GPU. 301 | 302 | So now we have an honest-to-god vertex buffer ready to read from. But it won't do us any good until we teach our pipeline how to interpret that data. 303 | 304 | To do that, we have to return to the `make_pipeline` function we wrote all the way back in https://www.falseidolfactory.com/2020/04/01/intro-to-gfx-hal-part-1-drawing-a-triangle.html#pipelines[Part 1] and extend it a little. 305 | 306 | In particular, we need to describe the vertex buffer and attributes in our primitive assembler: 307 | 308 | [source,rust] 309 | ---- 310 | tag::make_pipeline_fn_prim[] 311 | let primitive_assembler = { 312 | use gfx_hal::format::Format; 313 | use gfx_hal::pso::{AttributeDesc, Element, VertexBufferDesc, VertexInputRate}; 314 | 315 | PrimitiveAssemblerDesc::Vertex { 316 | ifeval::[{sourcepart} == 3] 317 | // We need to add a new section to our primitive assembler so 318 | // that it understands how to interpret the vertex data it is 319 | // given. 320 | // 321 | // We start by giving it a `binding` number, which is more or 322 | // less an ID or a slot for the vertex buffer. You'll see it 323 | // used later. 324 | // 325 | // The `stride` is the size of one item in the buffer. 326 | // The `rate` defines how to progress through the buffer. 327 | // Passing `Vertex` to this tells it to advance after every 328 | // vertex. This is usually what you want to do if you're not 329 | // making use of instanced rendering. 330 | endif::[] 331 | buffers: &[VertexBufferDesc { 332 | binding: 0, 333 | stride: std::mem::size_of::() as u32, 334 | rate: VertexInputRate::Vertex, 335 | }], 336 | 337 | ifeval::[{sourcepart} == 3] 338 | // Then we need to define the attributes _within_ the vertices. 339 | // For us this is the `position` and the `normal`. 340 | // 341 | // The vertex buffer we just defined has a `binding` number of 342 | // `0`. The `location` refers to the location in the `layout` 343 | // definition in the vertex shader. 344 | // 345 | // Finally the `element` describes the size and position of the 346 | // attribute. Both of our elements are 3-component 32-bit float 347 | // vectors, and so the `format` is `Rgb32Sfloat`. (I don't know 348 | // why it's `Rgb` and not `Xyz` or `Vec3` but here we are.) 349 | // 350 | // Note that the second attribute has an offset of `12` bytes, 351 | // because it has 3 4-byte floats before it (e.g. the previous 352 | // attribute). 353 | endif::[] 354 | attributes: &[ 355 | AttributeDesc { 356 | location: 0, 357 | binding: 0, 358 | element: Element { 359 | format: Format::Rgb32Sfloat, 360 | offset: 0, 361 | }, 362 | }, 363 | AttributeDesc { 364 | location: 1, 365 | binding: 0, 366 | element: Element { 367 | format: Format::Rgb32Sfloat, 368 | offset: 12, 369 | }, 370 | }, 371 | ], 372 | input_assembler: InputAssemblerDesc::new(Primitive::TriangleList), 373 | vertex: vs_entry, 374 | tessellation: None, 375 | geometry: None, 376 | } 377 | }; 378 | end::make_pipeline_fn_prim[] 379 | ---- 380 | 381 | Note the `binding` number of `0`, which is sort-of an ID number for this particular kind of vertex buffer we're describing. Each attribute is also tied to that same binding number to indicate that those are the attributes for this kind of vertex buffer. That number will also come up again in our rendering loop. 382 | 383 | Note also the `location` parameters in the attributes. These should match the ones in the vertex shader - so `0` for position, and `1` for normal. 384 | 385 | The `format` of both is `Rgb32Sfloat` (which is just a needlessly obtuse way of saying `vec3`), and the second one has a 12-byte `offset` (because the previous attribute is 12 bytes in size, the size of three `f32` values). 386 | 387 | With our vertex buffer created, and our primitive assembler extended, we now have everything we need to render a teapot. And you know what that means! 388 | 389 | That's right - more manual memory management! 390 | 391 | Because we created a buffer, and allocated memory for it - we want to make sure we clean those up at the end. Like we did in https://www.falseidolfactory.com/2020/04/01/intro-to-gfx-hal-part-1-drawing-a-triangle.html#memory-management[Part 1], we're going to add these two things to our `Resources` struct: 392 | 393 | [source,rust] 394 | ---- 395 | include::part-1-triangle.adoc[tag=resources_struct_start] 396 | 397 | // Add these two fields: 398 | tag::resources_struct_vertex_buffer[] 399 | vertex_buffer_memory: B::Memory, 400 | vertex_buffer: B::Buffer, 401 | end::resources_struct_vertex_buffer[] 402 | include::part-1-triangle.adoc[tag=resources_struct_end] 403 | ---- 404 | 405 | And we also have to update the `drop` method of the `ResourceHolder` struct to delete the buffer and free its memory: 406 | 407 | [source,rust] 408 | ---- 409 | include::part-1-triangle.adoc[tag=resource_holder_struct_start] 410 | 411 | // And these: 412 | tag::resource_holder_struct_vertex_buffer_take[] 413 | vertex_buffer_memory, 414 | vertex_buffer, 415 | end::resource_holder_struct_vertex_buffer_take[] 416 | 417 | include::part-1-triangle.adoc[tag=resource_holder_struct_mid] 418 | 419 | // And also this: 420 | tag::resource_holder_struct_vertex_buffer_destroy[] 421 | device.free_memory(vertex_buffer_memory); 422 | device.destroy_buffer(vertex_buffer); 423 | end::resource_holder_struct_vertex_buffer_destroy[] 424 | 425 | // ... 426 | ---- 427 | 428 | Finally, when we _create_ our Resources struct, we need to supply the vertex buffer and its memory: 429 | 430 | [source,rust] 431 | ---- 432 | include::part-1-triangle.adoc[tag=resources_start] 433 | 434 | // Don't forget these lines also: 435 | tag::resources_vertex_buffer[] 436 | vertex_buffer_memory, 437 | vertex_buffer, 438 | end::resources_vertex_buffer[] 439 | include::part-1-triangle.adoc[tag=resources_end] 440 | ---- 441 | 442 | Only now can we delve back into our rendering loop. 443 | 444 | Previously we defined a set of triangles to draw as a list of `PushConstants` structs. We're going to keep that strategy, but we changed the format of the `PushConstants` struct in our shader to include a transformation matrix. 445 | 446 | So we need to update the Rust equivalent to match it. We'll represent this 4x4 matrix with a 4x4 array-of-arrays: 447 | 448 | [source,rust] 449 | ---- 450 | tag::push_constants_struct[] 451 | ifndef::is_blog[] 452 | /// A struct representing the data that we want to supply in push constants. 453 | /// 454 | /// The `repr(C)` attribute is required to ensure that the memory layout is 455 | /// what we expect. Without it, no specific layout is guaranteed. 456 | endif::is_blog[] 457 | #[repr(C)] 458 | #[derive(Debug, Clone, Copy)] 459 | struct PushConstants { 460 | transform: [[f32; 4]; 4], 461 | } 462 | end::push_constants_struct[] 463 | ---- 464 | 465 | Let's also define a helper function to create those matrices so we don't have to do it by hand every time: 466 | 467 | [source,rust] 468 | ---- 469 | tag::matrix_helper[] 470 | /// Create a matrix that positions, scales, and rotates. 471 | fn make_transform(translate: [f32; 3], angle: f32, scale: f32) -> [[f32; 4]; 4] { 472 | let c = angle.cos() * scale; 473 | let s = angle.sin() * scale; 474 | let [dx, dy, dz] = translate; 475 | 476 | [ 477 | [c, 0., s, 0.], 478 | [0., scale, 0., 0.], 479 | [-s, 0., c, 0.], 480 | [dx, dy, dz, 1.], 481 | ] 482 | } 483 | end::matrix_helper[] 484 | ---- 485 | 486 | If you're not familiar with matrix math, that's fine - for these tutorials, you can just take my word that this works. 487 | 488 | Let's next replace the list of triangles we had before with a "list" of one single teapot. We'll also use the elapsed time to animate an `angle` parameter. This should let us animate our teapot rotating: 489 | 490 | [source,rust] 491 | ---- 492 | tag::push_constant_data[] 493 | let angle = start_time.elapsed().as_secs_f32(); 494 | 495 | ifdef::is_blog[] 496 | // This replaces `let triangles = ...` 497 | endif::is_blog[] 498 | let teapots = &[PushConstants { 499 | transform: make_transform([0., 0., 0.5], angle, 1.0), 500 | }]; 501 | end::push_constant_data[] 502 | ---- 503 | 504 | Finally, the actually drawing stuff part. Just before we begin the render pass, we can bind the vertex buffer we want to render from: 505 | 506 | [source,rust] 507 | ---- 508 | tag::bind_vertex_buffer[] 509 | ifeval::[{sourcepart} == 3] 510 | // This sets which vertex buffers will be used to render 511 | // from. For now we're only going to bind one. 512 | // 513 | // The first number is the `binding` number we set in our 514 | // pipeline. The `Vec` we pass as the second parameter 515 | // contains which ranges of which buffers will be bound. 516 | // In our case, we bind the `WHOLE` buffer. 517 | endif::[] 518 | command_buffer.bind_vertex_buffers( 519 | 0, 520 | vec![(&res.vertex_buffer, gfx_hal::buffer::SubRange::WHOLE)], 521 | ); 522 | end::bind_vertex_buffer[] 523 | ---- 524 | 525 | We then have to replace the loop we used to draw our triangles with a new one, and we have to make sure we draw the full range of vertices in the buffer. For the triangles, we always used `0..3`, but here, we want to use the number of vertices in the mesh: 526 | 527 | [source,rust] 528 | ---- 529 | // This replaces `for triangle in triangles { ... }` 530 | tag::draw_call[] 531 | for teapot in teapots { 532 | use gfx_hal::pso::ShaderStageFlags; 533 | 534 | command_buffer.push_graphics_constants( 535 | pipeline_layout, 536 | ShaderStageFlags::VERTEX, 537 | 0, 538 | push_constant_bytes(teapot), 539 | ); 540 | 541 | ifeval::[{sourcepart} == 3] 542 | // This is the number of vertices in the whole teapot. 543 | endif::[] 544 | let vertex_count = mesh.len() as u32; 545 | command_buffer.draw(0..vertex_count, 0..1); 546 | } 547 | end::draw_call[] 548 | ---- 549 | 550 | And with that, we are finished! 551 | 552 | Behold, like the background of a particularly underwhelming PlayStation demo disc - one multicolor teapot: 553 | -------------------------------------------------------------------------------- /src/bin/part-3-vertex-buffers.rs: -------------------------------------------------------------------------------- 1 | /// A struct representing the data that we want to supply in push constants. 2 | /// 3 | /// The `repr(C)` attribute is required to ensure that the memory layout is 4 | /// what we expect. Without it, no specific layout is guaranteed. 5 | #[repr(C)] 6 | #[derive(Debug, Clone, Copy)] 7 | struct PushConstants { 8 | transform: [[f32; 4]; 4], 9 | } 10 | 11 | /// A struct representing a single vertex in 3D space with a normal. 12 | /// 13 | /// The `repr(C)` attribute is required to ensure that the memory layout is 14 | /// what we expect. Without it, no specific layout is guaranteed. 15 | #[derive(serde::Deserialize)] 16 | #[repr(C)] 17 | struct Vertex { 18 | position: [f32; 3], 19 | normal: [f32; 3], 20 | } 21 | 22 | fn main() { 23 | use std::mem::ManuallyDrop; 24 | 25 | use gfx_hal::{ 26 | device::Device, 27 | window::{Extent2D, PresentationSurface, Surface}, 28 | Instance, 29 | }; 30 | use shaderc::ShaderKind; 31 | 32 | const APP_NAME: &'static str = "Part 3: Vertex buffers"; 33 | const WINDOW_SIZE: [u32; 2] = [512, 512]; 34 | 35 | let event_loop = winit::event_loop::EventLoop::new(); 36 | 37 | let (logical_window_size, physical_window_size) = { 38 | use winit::dpi::{LogicalSize, PhysicalSize}; 39 | 40 | let dpi = event_loop.primary_monitor().scale_factor(); 41 | let logical: LogicalSize = WINDOW_SIZE.into(); 42 | let physical: PhysicalSize = logical.to_physical(dpi); 43 | 44 | (logical, physical) 45 | }; 46 | 47 | let mut surface_extent = Extent2D { 48 | width: physical_window_size.width, 49 | height: physical_window_size.height, 50 | }; 51 | 52 | let window = winit::window::WindowBuilder::new() 53 | .with_title(APP_NAME) 54 | .with_inner_size(logical_window_size) 55 | .build(&event_loop) 56 | .expect("Failed to create window"); 57 | 58 | let (instance, surface, adapter) = { 59 | let instance = backend::Instance::create(APP_NAME, 1).expect("Backend not supported"); 60 | 61 | let surface = unsafe { 62 | instance 63 | .create_surface(&window) 64 | .expect("Failed to create surface for window") 65 | }; 66 | 67 | let adapter = instance.enumerate_adapters().remove(0); 68 | 69 | (instance, surface, adapter) 70 | }; 71 | 72 | let (device, mut queue_group) = { 73 | use gfx_hal::queue::QueueFamily; 74 | 75 | let queue_family = adapter 76 | .queue_families 77 | .iter() 78 | .find(|family| { 79 | surface.supports_queue_family(family) && family.queue_type().supports_graphics() 80 | }) 81 | .expect("No compatible queue family found"); 82 | 83 | let mut gpu = unsafe { 84 | use gfx_hal::adapter::PhysicalDevice; 85 | 86 | adapter 87 | .physical_device 88 | .open(&[(queue_family, &[1.0])], gfx_hal::Features::empty()) 89 | .expect("Failed to open device") 90 | }; 91 | 92 | (gpu.device, gpu.queue_groups.pop().unwrap()) 93 | }; 94 | 95 | let (command_pool, mut command_buffer) = unsafe { 96 | use gfx_hal::command::Level; 97 | use gfx_hal::pool::{CommandPool, CommandPoolCreateFlags}; 98 | 99 | let mut command_pool = device 100 | .create_command_pool(queue_group.family, CommandPoolCreateFlags::empty()) 101 | .expect("Out of memory"); 102 | 103 | let command_buffer = command_pool.allocate_one(Level::Primary); 104 | 105 | (command_pool, command_buffer) 106 | }; 107 | 108 | let surface_color_format = { 109 | use gfx_hal::format::{ChannelType, Format}; 110 | 111 | let supported_formats = surface 112 | .supported_formats(&adapter.physical_device) 113 | .unwrap_or(vec![]); 114 | 115 | let default_format = *supported_formats.get(0).unwrap_or(&Format::Rgba8Srgb); 116 | 117 | supported_formats 118 | .into_iter() 119 | .find(|format| format.base_format().1 == ChannelType::Srgb) 120 | .unwrap_or(default_format) 121 | }; 122 | 123 | // The `teapot_mesh.bin` is just a `Vec` that was serialized 124 | // using the `bincode` crate. So we can deserialize it directly. 125 | let binary_mesh_data = include_bytes!("../../assets/teapot_mesh.bin"); 126 | let mesh: Vec = 127 | bincode::deserialize(binary_mesh_data).expect("Failed to deserialize mesh"); 128 | 129 | /// Create an empty buffer with the given size and properties. 130 | /// 131 | /// Buffers can be used for various things. The `usage` parameter defines 132 | /// how the buffer should be treated (vertex buffer, index buffer, etc). 133 | /// The `properties` specify the kind of memory that should be used to 134 | /// store this buffer (CPU visible, device local, etc). 135 | unsafe fn make_buffer( 136 | device: &B::Device, 137 | physical_device: &B::PhysicalDevice, 138 | buffer_len: usize, 139 | usage: gfx_hal::buffer::Usage, 140 | properties: gfx_hal::memory::Properties, 141 | ) -> (B::Memory, B::Buffer) { 142 | use gfx_hal::{adapter::PhysicalDevice, MemoryTypeId}; 143 | 144 | // This creates a handle to a buffer. The `buffer_len` is in bytes, 145 | // and the usage states what kind of buffer it is. For this part, 146 | // we're making a vertex buffer, so you'll see later that we pass 147 | // `Usage::VERTEX` for this parameter. 148 | let mut buffer = device 149 | .create_buffer(buffer_len as u64, usage) 150 | .expect("Failed to create buffer"); 151 | 152 | // The device may have its own requirements for storing a buffer of 153 | // this certain size and properties. It returns a `Requirements` struct 154 | // from which we'll use two fields: `size` and `type_mask`. 155 | // 156 | // The `size` field should be pretty straightforward - it may differ 157 | // from `buffer_len` if there are padding/alignment requirements. 158 | // 159 | // The `type_mask` is a bitmask representing which memory types are 160 | // compatible. 161 | let req = device.get_buffer_requirements(&buffer); 162 | 163 | // This list of `memory_type` corresponds to the list represented by 164 | // the `type_mask` above. If the nth bit in the mask is `1`, then the 165 | // nth memory type in this list is supported. 166 | let memory_types = physical_device.memory_properties().memory_types; 167 | 168 | // We iterate over all the memory types and select the first one that 169 | // is both supported (e.g. in the `type_mask`), and supports the 170 | // `properties` we requested. In our case this is `CPU_VISIBLE` as 171 | // we'll see later. 172 | let memory_type = memory_types 173 | .iter() 174 | .enumerate() 175 | .find(|(id, mem_type)| { 176 | let type_supported = req.type_mask & (1_u32 << id) != 0; 177 | type_supported && mem_type.properties.contains(properties) 178 | }) 179 | .map(|(id, _ty)| MemoryTypeId(id)) 180 | .expect("No compatible memory type available"); 181 | 182 | // Now that we know the size and type of the memory to allocate, we can 183 | // go ahead and do so. 184 | let buffer_memory = device 185 | .allocate_memory(memory_type, req.size) 186 | .expect("Failed to allocate buffer memory"); 187 | 188 | // Now that we have memory to back our buffer, we can bind that buffer 189 | // handle to the memory. That buffer now has some actual storage 190 | // associated with it. 191 | device 192 | .bind_buffer_memory(&buffer_memory, 0, &mut buffer) 193 | .expect("Failed to bind buffer memory"); 194 | 195 | (buffer_memory, buffer) 196 | } 197 | 198 | // The size of our vertex data is the number of vertices times 199 | // the size of one `Vertex`. 200 | let vertex_buffer_len = mesh.len() * std::mem::size_of::(); 201 | 202 | // We create a buffer, specifying that it should be used to store vertex 203 | // data (hence the `Usage::VERTEX`) and that it should be visible to the 204 | // CPU so we can load data into it (hence the `Properties::CPU_VISIBLE`). 205 | let (vertex_buffer_memory, vertex_buffer) = unsafe { 206 | use gfx_hal::buffer::Usage; 207 | use gfx_hal::memory::Properties; 208 | 209 | make_buffer::( 210 | &device, 211 | &adapter.physical_device, 212 | vertex_buffer_len, 213 | Usage::VERTEX, 214 | Properties::CPU_VISIBLE, 215 | ) 216 | }; 217 | 218 | unsafe { 219 | use gfx_hal::memory::Segment; 220 | 221 | // Mapping the buffer memory gives us a pointer directly to the 222 | // contents of the buffer, which lets us easily copy data into it. 223 | // 224 | // We pass `Segment::ALL` to say that we want to map the *whole* 225 | // buffer, as opposed to just part of it. 226 | let mapped_memory = device 227 | .map_memory(&vertex_buffer_memory, Segment::ALL) 228 | .expect("Failed to map memory"); 229 | 230 | // Here we just copy `vertex_buffer_len` *from* the `mesh` data 231 | // *to* to the `mapped_memory`. 232 | std::ptr::copy_nonoverlapping(mesh.as_ptr() as *const u8, mapped_memory, vertex_buffer_len); 233 | 234 | // Flushing the mapped memory ensures that the data we wrote to the 235 | // memory actually makes it to the graphics device. The copy alone does 236 | // not guarantee this. 237 | // 238 | // Again, we could supply multiple ranges (of multiple buffers even) 239 | // but instead we just flush `ALL` of our single buffer. 240 | device 241 | .flush_mapped_memory_ranges(vec![(&vertex_buffer_memory, Segment::ALL)]) 242 | .expect("Out of memory"); 243 | 244 | device.unmap_memory(&vertex_buffer_memory); 245 | } 246 | 247 | let render_pass = { 248 | use gfx_hal::image::Layout; 249 | use gfx_hal::pass::{ 250 | Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp, SubpassDesc, 251 | }; 252 | 253 | let color_attachment = Attachment { 254 | format: Some(surface_color_format), 255 | samples: 1, 256 | ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store), 257 | stencil_ops: AttachmentOps::DONT_CARE, 258 | layouts: Layout::Undefined..Layout::Present, 259 | }; 260 | 261 | let subpass = SubpassDesc { 262 | colors: &[(0, Layout::ColorAttachmentOptimal)], 263 | depth_stencil: None, 264 | inputs: &[], 265 | resolves: &[], 266 | preserves: &[], 267 | }; 268 | 269 | unsafe { 270 | device 271 | .create_render_pass(&[color_attachment], &[subpass], &[]) 272 | .expect("Out of memory") 273 | } 274 | }; 275 | 276 | let pipeline_layout = unsafe { 277 | use gfx_hal::pso::ShaderStageFlags; 278 | 279 | let push_constant_bytes = std::mem::size_of::() as u32; 280 | 281 | device 282 | .create_pipeline_layout(&[], &[(ShaderStageFlags::VERTEX, 0..push_constant_bytes)]) 283 | .expect("Out of memory") 284 | }; 285 | 286 | let vertex_shader = include_str!("shaders/part-3.vert"); 287 | let fragment_shader = include_str!("shaders/part-3.frag"); 288 | 289 | /// Compile some GLSL shader source to SPIR-V. 290 | /// 291 | /// We tend to write shaders in high-level languages, but the GPU doesn't 292 | /// work with that directly. Instead, we can convert it to an intermediate 293 | /// representation: SPIR-V. This is more easily interpreted and optimized 294 | /// by your graphics card. As an added bonus, this allows us to use the 295 | /// same shader code across different backends. 296 | fn compile_shader(glsl: &str, shader_kind: ShaderKind) -> Vec { 297 | let mut compiler = shaderc::Compiler::new().unwrap(); 298 | 299 | let compiled_shader = compiler 300 | .compile_into_spirv(glsl, shader_kind, "unnamed", "main", None) 301 | .expect("Failed to compile shader"); 302 | 303 | compiled_shader.as_binary().to_vec() 304 | } 305 | 306 | /// Create a pipeline with the given layout and shaders. 307 | /// 308 | /// A pipeline contains nearly all the required information for rendering, 309 | /// and is only usable within the render pass it's defined for. 310 | unsafe fn make_pipeline( 311 | device: &B::Device, 312 | render_pass: &B::RenderPass, 313 | pipeline_layout: &B::PipelineLayout, 314 | vertex_shader: &str, 315 | fragment_shader: &str, 316 | ) -> B::GraphicsPipeline { 317 | use gfx_hal::pass::Subpass; 318 | use gfx_hal::pso::{ 319 | BlendState, ColorBlendDesc, ColorMask, EntryPoint, Face, GraphicsPipelineDesc, 320 | InputAssemblerDesc, Primitive, PrimitiveAssemblerDesc, Rasterizer, Specialization, 321 | }; 322 | let vertex_shader_module = device 323 | .create_shader_module(&compile_shader(vertex_shader, ShaderKind::Vertex)) 324 | .expect("Failed to create vertex shader module"); 325 | 326 | let fragment_shader_module = device 327 | .create_shader_module(&compile_shader(fragment_shader, ShaderKind::Fragment)) 328 | .expect("Failed to create fragment shader module"); 329 | 330 | let (vs_entry, fs_entry) = ( 331 | EntryPoint { 332 | entry: "main", 333 | module: &vertex_shader_module, 334 | specialization: Specialization::default(), 335 | }, 336 | EntryPoint { 337 | entry: "main", 338 | module: &fragment_shader_module, 339 | specialization: Specialization::default(), 340 | }, 341 | ); 342 | let primitive_assembler = { 343 | use gfx_hal::format::Format; 344 | use gfx_hal::pso::{AttributeDesc, Element, VertexBufferDesc, VertexInputRate}; 345 | 346 | PrimitiveAssemblerDesc::Vertex { 347 | // We need to add a new section to our primitive assembler so 348 | // that it understands how to interpret the vertex data it is 349 | // given. 350 | // 351 | // We start by giving it a `binding` number, which is more or 352 | // less an ID or a slot for the vertex buffer. You'll see it 353 | // used later. 354 | // 355 | // The `stride` is the size of one item in the buffer. 356 | // The `rate` defines how to progress through the buffer. 357 | // Passing `Vertex` to this tells it to advance after every 358 | // vertex. This is usually what you want to do if you're not 359 | // making use of instanced rendering. 360 | buffers: &[VertexBufferDesc { 361 | binding: 0, 362 | stride: std::mem::size_of::() as u32, 363 | rate: VertexInputRate::Vertex, 364 | }], 365 | 366 | // Then we need to define the attributes _within_ the vertices. 367 | // For us this is the `position` and the `normal`. 368 | // 369 | // The vertex buffer we just defined has a `binding` number of 370 | // `0`. The `location` refers to the location in the `layout` 371 | // definition in the vertex shader. 372 | // 373 | // Finally the `element` describes the size and position of the 374 | // attribute. Both of our elements are 3-component 32-bit float 375 | // vectors, and so the `format` is `Rgb32Sfloat`. (I don't know 376 | // why it's `Rgb` and not `Xyz` or `Vec3` but here we are.) 377 | // 378 | // Note that the second attribute has an offset of `12` bytes, 379 | // because it has 3 4-byte floats before it (e.g. the previous 380 | // attribute). 381 | attributes: &[ 382 | AttributeDesc { 383 | location: 0, 384 | binding: 0, 385 | element: Element { 386 | format: Format::Rgb32Sfloat, 387 | offset: 0, 388 | }, 389 | }, 390 | AttributeDesc { 391 | location: 1, 392 | binding: 0, 393 | element: Element { 394 | format: Format::Rgb32Sfloat, 395 | offset: 12, 396 | }, 397 | }, 398 | ], 399 | input_assembler: InputAssemblerDesc::new(Primitive::TriangleList), 400 | vertex: vs_entry, 401 | tessellation: None, 402 | geometry: None, 403 | } 404 | }; 405 | let mut pipeline_desc = GraphicsPipelineDesc::new( 406 | primitive_assembler, 407 | Rasterizer { 408 | cull_face: Face::BACK, 409 | ..Rasterizer::FILL 410 | }, 411 | Some(fs_entry), 412 | pipeline_layout, 413 | Subpass { 414 | index: 0, 415 | main_pass: render_pass, 416 | }, 417 | ); 418 | 419 | pipeline_desc.blender.targets.push(ColorBlendDesc { 420 | mask: ColorMask::ALL, 421 | blend: Some(BlendState::ALPHA), 422 | }); 423 | let pipeline = device 424 | .create_graphics_pipeline(&pipeline_desc, None) 425 | .expect("Failed to create graphics pipeline"); 426 | 427 | device.destroy_shader_module(vertex_shader_module); 428 | device.destroy_shader_module(fragment_shader_module); 429 | 430 | pipeline 431 | }; 432 | 433 | let pipeline = unsafe { 434 | make_pipeline::( 435 | &device, 436 | &render_pass, 437 | &pipeline_layout, 438 | vertex_shader, 439 | fragment_shader, 440 | ) 441 | }; 442 | 443 | let submission_complete_fence = device.create_fence(true).expect("Out of memory"); 444 | let rendering_complete_semaphore = device.create_semaphore().expect("Out of memory"); 445 | 446 | struct Resources { 447 | instance: B::Instance, 448 | surface: B::Surface, 449 | device: B::Device, 450 | render_passes: Vec, 451 | pipeline_layouts: Vec, 452 | pipelines: Vec, 453 | command_pool: B::CommandPool, 454 | submission_complete_fence: B::Fence, 455 | rendering_complete_semaphore: B::Semaphore, 456 | vertex_buffer_memory: B::Memory, 457 | vertex_buffer: B::Buffer, 458 | } 459 | 460 | struct ResourceHolder(ManuallyDrop>); 461 | 462 | impl Drop for ResourceHolder { 463 | fn drop(&mut self) { 464 | unsafe { 465 | let Resources { 466 | instance, 467 | mut surface, 468 | device, 469 | command_pool, 470 | render_passes, 471 | pipeline_layouts, 472 | pipelines, 473 | submission_complete_fence, 474 | rendering_complete_semaphore, 475 | vertex_buffer_memory, 476 | vertex_buffer, 477 | } = ManuallyDrop::take(&mut self.0); 478 | 479 | device.free_memory(vertex_buffer_memory); 480 | device.destroy_buffer(vertex_buffer); 481 | device.destroy_semaphore(rendering_complete_semaphore); 482 | device.destroy_fence(submission_complete_fence); 483 | for pipeline in pipelines { 484 | device.destroy_graphics_pipeline(pipeline); 485 | } 486 | for pipeline_layout in pipeline_layouts { 487 | device.destroy_pipeline_layout(pipeline_layout); 488 | } 489 | for render_pass in render_passes { 490 | device.destroy_render_pass(render_pass); 491 | } 492 | device.destroy_command_pool(command_pool); 493 | surface.unconfigure_swapchain(&device); 494 | instance.destroy_surface(surface); 495 | } 496 | } 497 | } 498 | 499 | let mut resource_holder: ResourceHolder = 500 | ResourceHolder(ManuallyDrop::new(Resources { 501 | instance, 502 | surface, 503 | device, 504 | command_pool, 505 | render_passes: vec![render_pass], 506 | pipeline_layouts: vec![pipeline_layout], 507 | pipelines: vec![pipeline], 508 | submission_complete_fence, 509 | rendering_complete_semaphore, 510 | vertex_buffer_memory, 511 | vertex_buffer, 512 | })); 513 | 514 | let start_time = std::time::Instant::now(); 515 | 516 | let mut should_configure_swapchain = true; 517 | 518 | event_loop.run(move |event, _, control_flow| { 519 | use winit::event::{Event, WindowEvent}; 520 | use winit::event_loop::ControlFlow; 521 | 522 | match event { 523 | Event::WindowEvent { event, .. } => match event { 524 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 525 | WindowEvent::Resized(dims) => { 526 | surface_extent = Extent2D { 527 | width: dims.width, 528 | height: dims.height, 529 | }; 530 | should_configure_swapchain = true; 531 | } 532 | WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { 533 | surface_extent = Extent2D { 534 | width: new_inner_size.width, 535 | height: new_inner_size.height, 536 | }; 537 | should_configure_swapchain = true; 538 | } 539 | _ => (), 540 | }, 541 | Event::MainEventsCleared => window.request_redraw(), 542 | Event::RedrawRequested(_) => { 543 | let res: &mut Resources<_> = &mut resource_holder.0; 544 | let render_pass = &res.render_passes[0]; 545 | let pipeline_layout = &res.pipeline_layouts[0]; 546 | let pipeline = &res.pipelines[0]; 547 | 548 | unsafe { 549 | use gfx_hal::pool::CommandPool; 550 | 551 | // We refuse to wait more than a second, to avoid hanging. 552 | let render_timeout_ns = 1_000_000_000; 553 | 554 | res.device 555 | .wait_for_fence(&res.submission_complete_fence, render_timeout_ns) 556 | .expect("Out of memory or device lost"); 557 | 558 | res.device 559 | .reset_fence(&res.submission_complete_fence) 560 | .expect("Out of memory"); 561 | 562 | res.command_pool.reset(false); 563 | } 564 | 565 | if should_configure_swapchain { 566 | use gfx_hal::window::SwapchainConfig; 567 | 568 | let caps = res.surface.capabilities(&adapter.physical_device); 569 | 570 | let mut swapchain_config = 571 | SwapchainConfig::from_caps(&caps, surface_color_format, surface_extent); 572 | 573 | // This seems to fix some fullscreen slowdown on macOS. 574 | if caps.image_count.contains(&3) { 575 | swapchain_config.image_count = 3; 576 | } 577 | 578 | surface_extent = swapchain_config.extent; 579 | 580 | unsafe { 581 | res.surface 582 | .configure_swapchain(&res.device, swapchain_config) 583 | .expect("Failed to configure swapchain"); 584 | }; 585 | 586 | should_configure_swapchain = false; 587 | } 588 | 589 | let surface_image = unsafe { 590 | // We refuse to wait more than a second, to avoid hanging. 591 | let acquire_timeout_ns = 1_000_000_000; 592 | 593 | match res.surface.acquire_image(acquire_timeout_ns) { 594 | Ok((image, _)) => image, 595 | Err(_) => { 596 | should_configure_swapchain = true; 597 | return; 598 | } 599 | } 600 | }; 601 | 602 | let framebuffer = unsafe { 603 | use std::borrow::Borrow; 604 | 605 | use gfx_hal::image::Extent; 606 | 607 | res.device 608 | .create_framebuffer( 609 | render_pass, 610 | vec![surface_image.borrow()], 611 | Extent { 612 | width: surface_extent.width, 613 | height: surface_extent.height, 614 | depth: 1, 615 | }, 616 | ) 617 | .unwrap() 618 | }; 619 | 620 | let viewport = { 621 | use gfx_hal::pso::{Rect, Viewport}; 622 | 623 | Viewport { 624 | rect: Rect { 625 | x: 0, 626 | y: 0, 627 | w: surface_extent.width as i16, 628 | h: surface_extent.height as i16, 629 | }, 630 | depth: 0.0..1.0, 631 | } 632 | }; 633 | 634 | /// Create a matrix that positions, scales, and rotates. 635 | fn make_transform(translate: [f32; 3], angle: f32, scale: f32) -> [[f32; 4]; 4] { 636 | let c = angle.cos() * scale; 637 | let s = angle.sin() * scale; 638 | let [dx, dy, dz] = translate; 639 | 640 | [ 641 | [c, 0., s, 0.], 642 | [0., scale, 0., 0.], 643 | [-s, 0., c, 0.], 644 | [dx, dy, dz, 1.], 645 | ] 646 | } 647 | 648 | let angle = start_time.elapsed().as_secs_f32(); 649 | 650 | let teapots = &[PushConstants { 651 | transform: make_transform([0., 0., 0.5], angle, 1.0), 652 | }]; 653 | 654 | /// Returns a view of a struct as a slice of `u32`s. 655 | /// 656 | /// Note that this assumes the struct divides evenly into 657 | /// 4-byte chunks. If the contents are all `f32`s, which they 658 | /// often are, then this will always be the case. 659 | unsafe fn push_constant_bytes(push_constants: &T) -> &[u32] { 660 | let size_in_bytes = std::mem::size_of::(); 661 | let size_in_u32s = size_in_bytes / std::mem::size_of::(); 662 | let start_ptr = push_constants as *const T as *const u32; 663 | std::slice::from_raw_parts(start_ptr, size_in_u32s) 664 | } 665 | 666 | unsafe { 667 | use gfx_hal::command::{ 668 | ClearColor, ClearValue, CommandBuffer, CommandBufferFlags, SubpassContents, 669 | }; 670 | 671 | command_buffer.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); 672 | 673 | command_buffer.set_viewports(0, &[viewport.clone()]); 674 | command_buffer.set_scissors(0, &[viewport.rect]); 675 | 676 | // This sets which vertex buffers will be used to render 677 | // from. For now we're only going to bind one. 678 | // 679 | // The first number is the `binding` number we set in our 680 | // pipeline. The `Vec` we pass as the second parameter 681 | // contains which ranges of which buffers will be bound. 682 | // In our case, we bind the `WHOLE` buffer. 683 | command_buffer.bind_vertex_buffers( 684 | 0, 685 | vec![(&res.vertex_buffer, gfx_hal::buffer::SubRange::WHOLE)], 686 | ); 687 | 688 | command_buffer.begin_render_pass( 689 | render_pass, 690 | &framebuffer, 691 | viewport.rect, 692 | &[ClearValue { 693 | color: ClearColor { 694 | float32: [0.0, 0.0, 0.0, 1.0], 695 | }, 696 | }], 697 | SubpassContents::Inline, 698 | ); 699 | 700 | command_buffer.bind_graphics_pipeline(pipeline); 701 | 702 | for teapot in teapots { 703 | use gfx_hal::pso::ShaderStageFlags; 704 | 705 | command_buffer.push_graphics_constants( 706 | pipeline_layout, 707 | ShaderStageFlags::VERTEX, 708 | 0, 709 | push_constant_bytes(teapot), 710 | ); 711 | 712 | // This is the number of vertices in the whole teapot. 713 | let vertex_count = mesh.len() as u32; 714 | command_buffer.draw(0..vertex_count, 0..1); 715 | } 716 | 717 | command_buffer.end_render_pass(); 718 | command_buffer.finish(); 719 | } 720 | 721 | unsafe { 722 | use gfx_hal::queue::{CommandQueue, Submission}; 723 | 724 | let submission = Submission { 725 | command_buffers: vec![&command_buffer], 726 | wait_semaphores: None, 727 | signal_semaphores: vec![&res.rendering_complete_semaphore], 728 | }; 729 | 730 | queue_group.queues[0].submit(submission, Some(&res.submission_complete_fence)); 731 | 732 | let result = queue_group.queues[0].present( 733 | &mut res.surface, 734 | surface_image, 735 | Some(&res.rendering_complete_semaphore), 736 | ); 737 | 738 | should_configure_swapchain |= result.is_err(); 739 | 740 | res.device.destroy_framebuffer(framebuffer); 741 | } 742 | } 743 | _ => (), 744 | } 745 | }); 746 | } 747 | -------------------------------------------------------------------------------- /src/bin/part-1-triangle.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use std::mem::ManuallyDrop; 3 | 4 | use gfx_hal::{ 5 | device::Device, 6 | window::{Extent2D, PresentationSurface, Surface}, 7 | Instance, 8 | }; 9 | use shaderc::ShaderKind; 10 | 11 | const APP_NAME: &'static str = "Part 1: Drawing a triangle"; 12 | const WINDOW_SIZE: [u32; 2] = [512, 512]; 13 | 14 | // Any `winit` application starts with an event loop. You need one of these 15 | // to create a window. 16 | let event_loop = winit::event_loop::EventLoop::new(); 17 | 18 | // Before we create a window, we also need to know what size to make it. 19 | // 20 | // Note that logical and physical window size are different though! 21 | // 22 | // Physical size is the real-life size of the display, in physical pixels. 23 | // Logical size is the scaled display, according to the OS. High-DPI 24 | // displays will present a smaller logical size, which you can scale up by 25 | // the DPI to determine the physical size. 26 | let (logical_window_size, physical_window_size) = { 27 | use winit::dpi::{LogicalSize, PhysicalSize}; 28 | 29 | let dpi = event_loop.primary_monitor().scale_factor(); 30 | let logical: LogicalSize = WINDOW_SIZE.into(); 31 | let physical: PhysicalSize = logical.to_physical(dpi); 32 | 33 | (logical, physical) 34 | }; 35 | 36 | // This will be the size of the final image we render, and therefore the 37 | // size of the surface we render to. 38 | // 39 | // We use the *physical* size because we want a rendered image that covers 40 | // every real pixel. 41 | let mut surface_extent = Extent2D { 42 | width: physical_window_size.width, 43 | height: physical_window_size.height, 44 | }; 45 | 46 | // We use the *logical* size to build the window because this will give a 47 | // consistent size on displays of different pixel densities. 48 | let window = winit::window::WindowBuilder::new() 49 | .with_title(APP_NAME) 50 | .with_inner_size(logical_window_size) 51 | .build(&event_loop) 52 | .expect("Failed to create window"); 53 | 54 | // The `instance` is an entry point to the graphics API. The `1` in the 55 | // call is a version number - we don't care about that for now. 56 | // 57 | // The `surface` is an abstraction of the OS window we're drawing into. 58 | // In `gfx`, it also manages the swap chain, which is a chain of 59 | // multiple images for us to render to. While one is being displayed, we 60 | // can write to another one - and then swap them, hence the name. 61 | // 62 | // The `adapter` represents a physical device. A graphics card for example. 63 | // The host may have more than one, but below, we just take the first. 64 | let (instance, surface, adapter) = { 65 | let instance = backend::Instance::create(APP_NAME, 1).expect("Backend not supported"); 66 | 67 | let surface = unsafe { 68 | instance 69 | .create_surface(&window) 70 | .expect("Failed to create surface for window") 71 | }; 72 | 73 | let adapter = instance.enumerate_adapters().remove(0); 74 | 75 | (instance, surface, adapter) 76 | }; 77 | 78 | let (device, mut queue_group) = { 79 | use gfx_hal::queue::QueueFamily; 80 | 81 | // We need a command queue to submit commands to the GPU. 82 | // Here we select the family (type) of queue we want. For rendering 83 | // (as opposed to compute, etc.) we need one that supports graphics. 84 | // We also of course need one that our surface supports. 85 | let queue_family = adapter 86 | .queue_families 87 | .iter() 88 | .find(|family| { 89 | surface.supports_queue_family(family) && family.queue_type().supports_graphics() 90 | }) 91 | .expect("No compatible queue family found"); 92 | 93 | // The `open` method returns us a logical `device`, and the set of 94 | // queue groups we asked for. 95 | // 96 | // A logical device is a view of the physical device, with or without 97 | // certain features. Features are similar to Rust features (optional 98 | // functionality) and in our example here, we don't request any. 99 | // 100 | // A `queue_group` is exactly what it sounds like. In the call below, 101 | // we're requesting one queue group of the above `queue_family`. We're 102 | // also asking for only one queue (because the list `&[1.0]` has only 103 | // one item) with priority `1.0`. The priorities are relative and so 104 | // are not important if you only have one queue. 105 | let mut gpu = unsafe { 106 | use gfx_hal::adapter::PhysicalDevice; 107 | 108 | adapter 109 | .physical_device 110 | .open(&[(queue_family, &[1.0])], gfx_hal::Features::empty()) 111 | .expect("Failed to open device") 112 | }; 113 | 114 | (gpu.device, gpu.queue_groups.pop().unwrap()) 115 | }; 116 | 117 | // Earlier we obtained a command queue to submit drawing commands to. The 118 | // data structure that carries those commands is called a `command_buffer`, 119 | // which are allocated from a `command_pool`. 120 | let (command_pool, mut command_buffer) = unsafe { 121 | use gfx_hal::command::Level; 122 | use gfx_hal::pool::{CommandPool, CommandPoolCreateFlags}; 123 | 124 | // To create our command pool, we have to specify the type of queue we 125 | // will be submitting it to. Luckily, we already have a queue and can 126 | // get the family from that. 127 | // 128 | // Ignore `CommandPoolCreateFlags` for now. 129 | let mut command_pool = device 130 | .create_command_pool(queue_group.family, CommandPoolCreateFlags::empty()) 131 | .expect("Out of memory"); 132 | 133 | // If we were planning to draw things in parallel or otherwise optimize 134 | // our command submissions, we might use more than one buffer. But for 135 | // now we'll just allocate a single one and re-use it for each frame. 136 | // 137 | // Level indicates whether it's a primary or secondary command buffer. 138 | // Secondary buffers are those nested within primary ones, but we don't 139 | // need to worry about that just now. 140 | let command_buffer = command_pool.allocate_one(Level::Primary); 141 | 142 | (command_pool, command_buffer) 143 | }; 144 | 145 | // We need to determine a format for the pixels in our surface image - 146 | // that is: what bytes, in what order, represent which color components. 147 | // 148 | // First we get a list of supported formats (where `None` means that any is 149 | // supported). Next, we try to pick one that supports SRGB, so that gamma 150 | // correction is handled for us. If we can't, we just pick the first one, 151 | // or default to `Rgba8Srgb`. 152 | let surface_color_format = { 153 | use gfx_hal::format::{ChannelType, Format}; 154 | 155 | let supported_formats = surface 156 | .supported_formats(&adapter.physical_device) 157 | .unwrap_or(vec![]); 158 | 159 | let default_format = *supported_formats.get(0).unwrap_or(&Format::Rgba8Srgb); 160 | 161 | supported_formats 162 | .into_iter() 163 | .find(|format| format.base_format().1 == ChannelType::Srgb) 164 | .unwrap_or(default_format) 165 | }; 166 | 167 | // A render pass defines which attachments (images) are to be used for 168 | // what purposes. Right now, we only have a color attachment for the final 169 | // output, but eventually we might have depth/stencil attachments, or even 170 | // other color attachments for other purposes. 171 | let render_pass = { 172 | use gfx_hal::image::Layout; 173 | use gfx_hal::pass::{ 174 | Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp, SubpassDesc, 175 | }; 176 | 177 | // This is an attachment for the final output. Note that it must have 178 | // the same pixel format as our surface. It has `1` sample-per-pixel 179 | // (which isn't worth thinking about too much). 180 | // 181 | // The `ops` parameter describes what to do to the image at the start 182 | // and end of the render pass (for color and depth). We want to `Clear` 183 | // it first, and then `Store` our rendered pixels to it at the end. 184 | // 185 | // The `stencil_ops` are the same, but for the stencil buffer, which we 186 | // aren't using yet. 187 | // 188 | // The `layouts` parameter defines the before and after layouts for the 189 | // image - essentially how it is laid out in memory. This is only a 190 | // hint and mostly for optimisation. Here, we know we're going to 191 | // `Present` the image to the window, so we want a layout optimised for 192 | // that by the end. 193 | let color_attachment = Attachment { 194 | format: Some(surface_color_format), 195 | samples: 1, 196 | ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store), 197 | stencil_ops: AttachmentOps::DONT_CARE, 198 | layouts: Layout::Undefined..Layout::Present, 199 | }; 200 | 201 | // A render pass could have multiple subpasses to it, but here we only 202 | // want one. The `0` is an id - an index into the final list of 203 | // attachments. It means we're using attachment `0` as a color 204 | // attachment. 205 | // 206 | // The `Layout` is the layout to be used *during* the render pass. 207 | let subpass = SubpassDesc { 208 | colors: &[(0, Layout::ColorAttachmentOptimal)], 209 | depth_stencil: None, 210 | inputs: &[], 211 | resolves: &[], 212 | preserves: &[], 213 | }; 214 | 215 | unsafe { 216 | // Note that we're passing a list of attachments here. 217 | // 218 | // The attachment in index `0` - `color_attachment` - will be 219 | // bound as a color attachment, because the subpass above 220 | // specifies the id `0`. 221 | // 222 | // The third parameter is for expressing `dependencies` between 223 | // subpasses, which we don't need. 224 | device 225 | .create_render_pass(&[color_attachment], &[subpass], &[]) 226 | .expect("Out of memory") 227 | } 228 | }; 229 | 230 | let pipeline_layout = unsafe { 231 | device 232 | .create_pipeline_layout(&[], &[]) 233 | .expect("Out of memory") 234 | }; 235 | 236 | let vertex_shader = include_str!("shaders/part-1.vert"); 237 | let fragment_shader = include_str!("shaders/part-1.frag"); 238 | 239 | /// Compile some GLSL shader source to SPIR-V. 240 | /// 241 | /// We tend to write shaders in high-level languages, but the GPU doesn't 242 | /// work with that directly. Instead, we can convert it to an intermediate 243 | /// representation: SPIR-V. This is more easily interpreted and optimized 244 | /// by your graphics card. As an added bonus, this allows us to use the 245 | /// same shader code across different backends. 246 | fn compile_shader(glsl: &str, shader_kind: ShaderKind) -> Vec { 247 | let mut compiler = shaderc::Compiler::new().unwrap(); 248 | 249 | // The `compile_into_spirv` function is pretty straightforward. 250 | // It optionally takes a filename (which we haven't, hence "unnamed"). 251 | // It also takes the entry point of the shader ("main"), and some 252 | // other compiler options which we're also ignoring (`None`). 253 | let compiled_shader = compiler 254 | .compile_into_spirv(glsl, shader_kind, "unnamed", "main", None) 255 | .expect("Failed to compile shader"); 256 | 257 | // The result is an opaque object. We can use the `as_binary` method to 258 | // get a `&[u32]` view of it, and then convert it to an owned `Vec`. 259 | compiled_shader.as_binary().to_vec() 260 | } 261 | 262 | /// Create a pipeline with the given layout and shaders. 263 | /// 264 | /// A pipeline contains nearly all the required information for rendering, 265 | /// and is only usable within the render pass it's defined for. 266 | unsafe fn make_pipeline( 267 | device: &B::Device, 268 | render_pass: &B::RenderPass, 269 | pipeline_layout: &B::PipelineLayout, 270 | vertex_shader: &str, 271 | fragment_shader: &str, 272 | ) -> B::GraphicsPipeline { 273 | use gfx_hal::pass::Subpass; 274 | use gfx_hal::pso::{ 275 | BlendState, ColorBlendDesc, ColorMask, EntryPoint, Face, GraphicsPipelineDesc, 276 | InputAssemblerDesc, Primitive, PrimitiveAssemblerDesc, Rasterizer, Specialization, 277 | }; 278 | let vertex_shader_module = device 279 | .create_shader_module(&compile_shader(vertex_shader, ShaderKind::Vertex)) 280 | .expect("Failed to create vertex shader module"); 281 | 282 | let fragment_shader_module = device 283 | .create_shader_module(&compile_shader(fragment_shader, ShaderKind::Fragment)) 284 | .expect("Failed to create fragment shader module"); 285 | 286 | // Shader modules are re-usable, and we could choose to define multiple 287 | // entry functions or multiple different specialized versions for 288 | // different pipelines. We specify which to use with the `EntryPoint` 289 | // struct here. 290 | // 291 | // The `entry` parameter here refers to the name of the function in the 292 | // shader that serves as the entry point. 293 | // 294 | // The `specialization` parameter allows you to tweak specific 295 | // constants in the shaders. That's not in scope for this part, so we 296 | // just use the empty default. 297 | let (vs_entry, fs_entry) = ( 298 | EntryPoint { 299 | entry: "main", 300 | module: &vertex_shader_module, 301 | specialization: Specialization::default(), 302 | }, 303 | EntryPoint { 304 | entry: "main", 305 | module: &fragment_shader_module, 306 | specialization: Specialization::default(), 307 | }, 308 | ); 309 | // We're not using vertex buffers or attributes, and we're definitely 310 | // not using tesselation/geometry shaders. So for now, all we have to 311 | // specify is that we want to render a `TriangleList` using the vertex 312 | // shader (`vs_entry`) that we loaded before. 313 | let primitive_assembler = PrimitiveAssemblerDesc::Vertex { 314 | buffers: &[], 315 | attributes: &[], 316 | input_assembler: InputAssemblerDesc::new(Primitive::TriangleList), 317 | vertex: vs_entry, 318 | tessellation: None, 319 | geometry: None, 320 | }; 321 | // Here is where we configure our pipeline. The `new` function sets the 322 | // required properties, after which we can add additional sections to 323 | // define what kind of render targets/attachments and vertex buffers it 324 | // accepts. 325 | let mut pipeline_desc = GraphicsPipelineDesc::new( 326 | primitive_assembler, 327 | Rasterizer { 328 | cull_face: Face::BACK, 329 | ..Rasterizer::FILL 330 | }, 331 | Some(fs_entry), 332 | pipeline_layout, 333 | Subpass { 334 | index: 0, 335 | main_pass: render_pass, 336 | }, 337 | ); 338 | 339 | // Here we specify that our pipeline will render to a color attachment. 340 | // The `mask` defines which color channels (red, green, blue, alpha) it 341 | // will write, and the `blend` parameter specifies how to blend the 342 | // rendered pixel with the existing pixel in the attachment. 343 | // 344 | // In this case, the `BlendState::ALPHA` preset says to blend them 345 | // based on their alpha values, which is usually what you want. 346 | pipeline_desc.blender.targets.push(ColorBlendDesc { 347 | mask: ColorMask::ALL, 348 | blend: Some(BlendState::ALPHA), 349 | }); 350 | let pipeline = device 351 | .create_graphics_pipeline(&pipeline_desc, None) 352 | .expect("Failed to create graphics pipeline"); 353 | 354 | // Once the pipeline is created, we no longer need to keep 355 | // the shader modules in memory. In theory, we could keep 356 | // them around for creating other pipelines with the same 357 | // shaders, but we don't need to. 358 | device.destroy_shader_module(vertex_shader_module); 359 | device.destroy_shader_module(fragment_shader_module); 360 | 361 | pipeline 362 | }; 363 | 364 | let pipeline = unsafe { 365 | make_pipeline::( 366 | &device, 367 | &render_pass, 368 | &pipeline_layout, 369 | vertex_shader, 370 | fragment_shader, 371 | ) 372 | }; 373 | 374 | // Since the GPU may operate asynchronously, there are a few important 375 | // things we have to synchronize. We use _fences_ to synchronize the CPU 376 | // with the GPU, and we use _semaphores_ to synchronize separate processes 377 | // within the GPU. 378 | // 379 | // Firstly, we have to ensure that our GPU commands have been submitted to 380 | // the queue before we re-use the command buffer. This is what the 381 | // `submission_complete_fence` is for. 382 | // 383 | // Secondly, we have to ensure that our image has been rendered before we 384 | // display it on the screen. 385 | // This is what the `rendering_complete_semaphore` is for. 386 | let submission_complete_fence = device.create_fence(true).expect("Out of memory"); 387 | let rendering_complete_semaphore = device.create_semaphore().expect("Out of memory"); 388 | 389 | struct Resources { 390 | instance: B::Instance, 391 | surface: B::Surface, 392 | device: B::Device, 393 | render_passes: Vec, 394 | pipeline_layouts: Vec, 395 | pipelines: Vec, 396 | command_pool: B::CommandPool, 397 | submission_complete_fence: B::Fence, 398 | rendering_complete_semaphore: B::Semaphore, 399 | } 400 | 401 | // We put the resources in an `ManuallyDrop` so that we can `take` the 402 | // contents later and destroy them. 403 | struct ResourceHolder(ManuallyDrop>); 404 | 405 | impl Drop for ResourceHolder { 406 | fn drop(&mut self) { 407 | unsafe { 408 | // We are moving the `Resources` out of the struct... 409 | let Resources { 410 | instance, 411 | mut surface, 412 | device, 413 | command_pool, 414 | render_passes, 415 | pipeline_layouts, 416 | pipelines, 417 | submission_complete_fence, 418 | rendering_complete_semaphore, 419 | } = ManuallyDrop::take(&mut self.0); 420 | 421 | // ... and destroying them individually: 422 | device.destroy_semaphore(rendering_complete_semaphore); 423 | device.destroy_fence(submission_complete_fence); 424 | for pipeline in pipelines { 425 | device.destroy_graphics_pipeline(pipeline); 426 | } 427 | for pipeline_layout in pipeline_layouts { 428 | device.destroy_pipeline_layout(pipeline_layout); 429 | } 430 | for render_pass in render_passes { 431 | device.destroy_render_pass(render_pass); 432 | } 433 | device.destroy_command_pool(command_pool); 434 | surface.unconfigure_swapchain(&device); 435 | instance.destroy_surface(surface); 436 | } 437 | } 438 | } 439 | 440 | let mut resource_holder: ResourceHolder = 441 | ResourceHolder(ManuallyDrop::new(Resources { 442 | instance, 443 | surface, 444 | device, 445 | command_pool, 446 | render_passes: vec![render_pass], 447 | pipeline_layouts: vec![pipeline_layout], 448 | pipelines: vec![pipeline], 449 | submission_complete_fence, 450 | rendering_complete_semaphore, 451 | })); 452 | 453 | // This will be very important later! It must be initialized to `true` so 454 | // that we rebuild the swapchain on the first frame. 455 | let mut should_configure_swapchain = true; 456 | 457 | // Note that this takes a `move` closure. This means it will take ownership 458 | // over any resources referenced within. It also means they will be dropped 459 | // only when the application is quit. 460 | event_loop.run(move |event, _, control_flow| { 461 | use winit::event::{Event, WindowEvent}; 462 | use winit::event_loop::ControlFlow; 463 | 464 | match event { 465 | Event::WindowEvent { event, .. } => match event { 466 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 467 | // If the window changes size, or the display changes 468 | // DPI / scale-factor, then the *physical* size will change, 469 | // which means our surface needs updated too. 470 | // 471 | // When the surface changes size, we need to rebuild the 472 | // swapchain so that its images are the right size. 473 | WindowEvent::Resized(dims) => { 474 | surface_extent = Extent2D { 475 | width: dims.width, 476 | height: dims.height, 477 | }; 478 | should_configure_swapchain = true; 479 | } 480 | WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { 481 | surface_extent = Extent2D { 482 | width: new_inner_size.width, 483 | height: new_inner_size.height, 484 | }; 485 | should_configure_swapchain = true; 486 | } 487 | _ => (), 488 | }, 489 | // In an interactive application, you would handle your logic 490 | // updates here. 491 | // 492 | // Right now, we just want to redraw the window each frame 493 | // and that's all. 494 | Event::MainEventsCleared => window.request_redraw(), 495 | Event::RedrawRequested(_) => { 496 | // We will need to reference our resources in our rendering 497 | // commands. 498 | // 499 | // Because I'm lazy and we're storing resources in `Vec`s, 500 | // we also take references to the contents here to avoid 501 | // confusing ourselves with different indices later. 502 | let res: &mut Resources<_> = &mut resource_holder.0; 503 | let render_pass = &res.render_passes[0]; 504 | let pipeline = &res.pipelines[0]; 505 | 506 | unsafe { 507 | use gfx_hal::pool::CommandPool; 508 | 509 | // We refuse to wait more than a second, to avoid hanging. 510 | let render_timeout_ns = 1_000_000_000; 511 | 512 | // Graphics commands may execute asynchronously, so to 513 | // ensure we're finished rendering the previous frame 514 | // before starting this new one, we wait here for the 515 | // rendering to signal the `submission_complete_fence` from 516 | // the previous frame. 517 | // 518 | // This may not be the most efficient option - say if you 519 | // wanted to render more than one frame simulatneously 520 | // - but for our example, it simplifies things. 521 | res.device 522 | .wait_for_fence(&res.submission_complete_fence, render_timeout_ns) 523 | .expect("Out of memory or device lost"); 524 | 525 | // Once the fence has been signalled, we must reset it 526 | res.device 527 | .reset_fence(&res.submission_complete_fence) 528 | .expect("Out of memory"); 529 | 530 | // This clears out the previous frame's command buffer and 531 | // returns it to the pool for use this frame. 532 | res.command_pool.reset(false); 533 | } 534 | 535 | // If the window is resized, or the rendering context is 536 | // otherwise invalidated, we may need to recreate our whole 537 | // swapchain. 538 | // 539 | // For now, all that entails is calling the 540 | // `configure_swapchain` method with the correct config, but 541 | // in future parts, we may have to recreate other resources 542 | // here. 543 | if should_configure_swapchain { 544 | use gfx_hal::window::SwapchainConfig; 545 | 546 | let caps = res.surface.capabilities(&adapter.physical_device); 547 | 548 | // We pass our `surface_extent` as a desired default, but 549 | // it may return us a different value, depending on what it 550 | // supports. 551 | let mut swapchain_config = 552 | SwapchainConfig::from_caps(&caps, surface_color_format, surface_extent); 553 | 554 | // If our device supports having 3 images in our swapchain, 555 | // then we want to use that. 556 | // 557 | // This seems to fix some fullscreen slowdown on macOS. 558 | if caps.image_count.contains(&3) { 559 | swapchain_config.image_count = 3; 560 | } 561 | 562 | // In case the surface returned an extent different from 563 | // the size we requested, we update our value. 564 | surface_extent = swapchain_config.extent; 565 | 566 | unsafe { 567 | res.surface 568 | .configure_swapchain(&res.device, swapchain_config) 569 | .expect("Failed to configure swapchain"); 570 | }; 571 | 572 | should_configure_swapchain = false; 573 | } 574 | 575 | // Our swapchain consists of two or more images. We want to 576 | // display one of them on screen, and then render to a 577 | // different one so we can swap them out smoothly. The 578 | // `acquire_image` method gives us a free one to render on. 579 | // 580 | // If it fails, there could be an issue with our swapchain, so 581 | // we early-out and rebuild it for next frame. 582 | let surface_image = unsafe { 583 | // We refuse to wait more than a second, to avoid hanging. 584 | let acquire_timeout_ns = 1_000_000_000; 585 | 586 | match res.surface.acquire_image(acquire_timeout_ns) { 587 | Ok((image, _)) => image, 588 | Err(_) => { 589 | should_configure_swapchain = true; 590 | return; 591 | } 592 | } 593 | }; 594 | 595 | // The Vulkan API, which `gfx` is based on, doesn't allow you 596 | // to render directly to images. Instead, you render to an 597 | // abstract framebuffer which represents your render target. 598 | // In practice, there may be no difference in our case, but 599 | // it's somthing to be aware of. 600 | let framebuffer = unsafe { 601 | use std::borrow::Borrow; 602 | 603 | use gfx_hal::image::Extent; 604 | 605 | res.device 606 | .create_framebuffer( 607 | render_pass, 608 | vec![surface_image.borrow()], 609 | Extent { 610 | width: surface_extent.width, 611 | height: surface_extent.height, 612 | depth: 1, 613 | }, 614 | ) 615 | .unwrap() 616 | }; 617 | 618 | // A viewport defines the rectangular section of the screen 619 | // to draw into. Here we're specifying the whole screen. 620 | // This will be used once we start rendering. 621 | let viewport = { 622 | use gfx_hal::pso::{Rect, Viewport}; 623 | 624 | Viewport { 625 | rect: Rect { 626 | x: 0, 627 | y: 0, 628 | w: surface_extent.width as i16, 629 | h: surface_extent.height as i16, 630 | }, 631 | depth: 0.0..1.0, 632 | } 633 | }; 634 | 635 | unsafe { 636 | use gfx_hal::command::{ 637 | ClearColor, ClearValue, CommandBuffer, CommandBufferFlags, SubpassContents, 638 | }; 639 | 640 | // This is how we start our command buffer. We set a 641 | // flag telling it we're only going to submit it once, 642 | // rather than submit the same commands over and over. 643 | command_buffer.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); 644 | 645 | // This is how we specify which part of the surface 646 | // we are drawing into. Changing the viewport will stretch 647 | // the resulting image into that rect. Changing the scissor 648 | // will crop it. 649 | command_buffer.set_viewports(0, &[viewport.clone()]); 650 | command_buffer.set_scissors(0, &[viewport.rect]); 651 | 652 | // Here we say which render pass we're in. This 653 | // defines which framebuffer (images) we'll draw to, and 654 | // also specifies what color to clear them to first, if 655 | // they have been configured to be cleared. 656 | command_buffer.begin_render_pass( 657 | render_pass, 658 | &framebuffer, 659 | viewport.rect, 660 | &[ClearValue { 661 | color: ClearColor { 662 | float32: [0.0, 0.0, 0.0, 1.0], 663 | }, 664 | }], 665 | SubpassContents::Inline, 666 | ); 667 | 668 | // This sets the pipeline that will be used to draw. 669 | // We can change this whenever we like, but it can be 670 | // inefficient to do so. Regardless, we only have one right 671 | // now. 672 | command_buffer.bind_graphics_pipeline(pipeline); 673 | 674 | // This is the command that actually tells the GPU to draw 675 | // some triangles. The `0..3` in the first parameter means 676 | // "draw vertices 0, 1, and 2". (For now, all those numbers 677 | // refer to is the `gl_VertexIndex` parameter in our vertex 678 | // shader. 679 | // The second parameter means "draw instance 0". Ignore 680 | // that for now as we're not using instanced rendering. 681 | command_buffer.draw(0..3, 0..1); 682 | 683 | // Here we finish our only render pass. We could begin 684 | // another, but since we're done, we also close off the 685 | // command buffer, which is now ready to submit to the GPU. 686 | command_buffer.end_render_pass(); 687 | command_buffer.finish(); 688 | } 689 | 690 | unsafe { 691 | use gfx_hal::queue::{CommandQueue, Submission}; 692 | 693 | // A `Submission` contains references to the command 694 | // buffers to submit, and also any semaphores used for 695 | // scheduling. 696 | // 697 | // If you wanted to ensure a previous submission was 698 | // complete before starting this one, you could add 699 | // `wait_semaphores`. 700 | // 701 | // In our case though, all we want to do is tell 702 | // `rendering_complete_semaphore` when we're done. 703 | let submission = Submission { 704 | command_buffers: vec![&command_buffer], 705 | wait_semaphores: None, 706 | signal_semaphores: vec![&res.rendering_complete_semaphore], 707 | }; 708 | 709 | // Commands must be submitted to an appropriate queue. We 710 | // requested a graphics queue, and so we are submitting 711 | // graphics commands. 712 | // 713 | // We tell the submission to notify 714 | // `submission_complete_fence` when the submission is 715 | // complete, at which point we can reclaim the command 716 | // buffer we used for next frame. 717 | queue_group.queues[0].submit(submission, Some(&res.submission_complete_fence)); 718 | 719 | // Finally, the `present` takes the output of our 720 | // rendering and displays it onscreen. We pass the 721 | // `rendering_complete_semaphore` so that we can be sure 722 | // the image we want to display has been rendered. 723 | let result = queue_group.queues[0].present( 724 | &mut res.surface, 725 | surface_image, 726 | Some(&res.rendering_complete_semaphore), 727 | ); 728 | 729 | // If presenting failed, it could be a problem with the 730 | // swapchain. For example, if the window was resized, our 731 | // image is no longer the correct dimensions. 732 | // 733 | // In the hopes that we can avoid the same error next 734 | // frame, we'll rebuild the swapchain. 735 | should_configure_swapchain |= result.is_err(); 736 | 737 | // We created this at the start of the frame 738 | // so we should destroy it too to avoid leaking it. 739 | res.device.destroy_framebuffer(framebuffer); 740 | } 741 | } 742 | _ => (), 743 | } 744 | }); 745 | } 746 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.2.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 8 | 9 | [[package]] 10 | name = "andrew" 11 | version = "0.2.1" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "9b7f09f89872c2b6b29e319377b1fbe91c6f5947df19a25596e121cf19a7b35e" 14 | dependencies = [ 15 | "bitflags", 16 | "line_drawing", 17 | "rusttype 0.7.9", 18 | "walkdir", 19 | "xdg", 20 | "xml-rs", 21 | ] 22 | 23 | [[package]] 24 | name = "android_glue" 25 | version = "0.2.3" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" 28 | 29 | [[package]] 30 | name = "approx" 31 | version = "0.3.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" 34 | dependencies = [ 35 | "num-traits", 36 | ] 37 | 38 | [[package]] 39 | name = "arrayvec" 40 | version = "0.5.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 43 | 44 | [[package]] 45 | name = "ash" 46 | version = "0.31.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "c69a8137596e84c22d57f3da1b5de1d4230b1742a710091c85f4d7ce50f00f38" 49 | dependencies = [ 50 | "libloading", 51 | ] 52 | 53 | [[package]] 54 | name = "autocfg" 55 | version = "1.0.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 58 | 59 | [[package]] 60 | name = "bincode" 61 | version = "1.3.1" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" 64 | dependencies = [ 65 | "byteorder", 66 | "serde", 67 | ] 68 | 69 | [[package]] 70 | name = "bitflags" 71 | version = "1.2.1" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 74 | 75 | [[package]] 76 | name = "block" 77 | version = "0.1.6" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 80 | 81 | [[package]] 82 | name = "bumpalo" 83 | version = "3.4.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" 86 | 87 | [[package]] 88 | name = "bytemuck" 89 | version = "1.4.1" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac" 92 | 93 | [[package]] 94 | name = "byteorder" 95 | version = "1.3.4" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 98 | 99 | [[package]] 100 | name = "calloop" 101 | version = "0.4.4" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "7aa2097be53a00de9e8fc349fea6d76221f398f5c4fa550d420669906962d160" 104 | dependencies = [ 105 | "mio", 106 | "mio-extras", 107 | "nix", 108 | ] 109 | 110 | [[package]] 111 | name = "cc" 112 | version = "1.0.60" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" 115 | dependencies = [ 116 | "jobserver", 117 | ] 118 | 119 | [[package]] 120 | name = "cfg-if" 121 | version = "0.1.10" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 124 | 125 | [[package]] 126 | name = "cloudabi" 127 | version = "0.0.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 130 | dependencies = [ 131 | "bitflags", 132 | ] 133 | 134 | [[package]] 135 | name = "cloudabi" 136 | version = "0.1.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" 139 | dependencies = [ 140 | "bitflags", 141 | ] 142 | 143 | [[package]] 144 | name = "cmake" 145 | version = "0.1.44" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "0e56268c17a6248366d66d4a47a3381369d068cce8409bb1716ed77ea32163bb" 148 | dependencies = [ 149 | "cc", 150 | ] 151 | 152 | [[package]] 153 | name = "cocoa" 154 | version = "0.19.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "f29f7768b2d1be17b96158e3285951d366b40211320fb30826a76cb7a0da6400" 157 | dependencies = [ 158 | "bitflags", 159 | "block", 160 | "core-foundation 0.6.4", 161 | "core-graphics 0.17.3", 162 | "foreign-types", 163 | "libc", 164 | "objc", 165 | ] 166 | 167 | [[package]] 168 | name = "cocoa-foundation" 169 | version = "0.1.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" 172 | dependencies = [ 173 | "bitflags", 174 | "block", 175 | "core-foundation 0.9.1", 176 | "core-graphics-types", 177 | "foreign-types", 178 | "libc", 179 | "objc", 180 | ] 181 | 182 | [[package]] 183 | name = "color_quant" 184 | version = "1.0.1" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" 187 | 188 | [[package]] 189 | name = "copyless" 190 | version = "0.1.5" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" 193 | 194 | [[package]] 195 | name = "core-foundation" 196 | version = "0.6.4" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" 199 | dependencies = [ 200 | "core-foundation-sys 0.6.2", 201 | "libc", 202 | ] 203 | 204 | [[package]] 205 | name = "core-foundation" 206 | version = "0.7.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" 209 | dependencies = [ 210 | "core-foundation-sys 0.7.0", 211 | "libc", 212 | ] 213 | 214 | [[package]] 215 | name = "core-foundation" 216 | version = "0.9.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" 219 | dependencies = [ 220 | "core-foundation-sys 0.8.1", 221 | "libc", 222 | ] 223 | 224 | [[package]] 225 | name = "core-foundation-sys" 226 | version = "0.6.2" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" 229 | 230 | [[package]] 231 | name = "core-foundation-sys" 232 | version = "0.7.0" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" 235 | 236 | [[package]] 237 | name = "core-foundation-sys" 238 | version = "0.8.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "c0af3b5e4601de3837c9332e29e0aae47a0d46ebfa246d12b82f564bac233393" 241 | 242 | [[package]] 243 | name = "core-graphics" 244 | version = "0.17.3" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" 247 | dependencies = [ 248 | "bitflags", 249 | "core-foundation 0.6.4", 250 | "foreign-types", 251 | "libc", 252 | ] 253 | 254 | [[package]] 255 | name = "core-graphics" 256 | version = "0.19.2" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" 259 | dependencies = [ 260 | "bitflags", 261 | "core-foundation 0.7.0", 262 | "foreign-types", 263 | "libc", 264 | ] 265 | 266 | [[package]] 267 | name = "core-graphics-types" 268 | version = "0.1.1" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" 271 | dependencies = [ 272 | "bitflags", 273 | "core-foundation 0.9.1", 274 | "foreign-types", 275 | "libc", 276 | ] 277 | 278 | [[package]] 279 | name = "core-video-sys" 280 | version = "0.1.4" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" 283 | dependencies = [ 284 | "cfg-if", 285 | "core-foundation-sys 0.7.0", 286 | "core-graphics 0.19.2", 287 | "libc", 288 | "objc", 289 | ] 290 | 291 | [[package]] 292 | name = "crc32fast" 293 | version = "1.2.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 296 | dependencies = [ 297 | "cfg-if", 298 | ] 299 | 300 | [[package]] 301 | name = "crossbeam-channel" 302 | version = "0.4.4" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" 305 | dependencies = [ 306 | "crossbeam-utils", 307 | "maybe-uninit", 308 | ] 309 | 310 | [[package]] 311 | name = "crossbeam-deque" 312 | version = "0.7.3" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" 315 | dependencies = [ 316 | "crossbeam-epoch", 317 | "crossbeam-utils", 318 | "maybe-uninit", 319 | ] 320 | 321 | [[package]] 322 | name = "crossbeam-epoch" 323 | version = "0.8.2" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 326 | dependencies = [ 327 | "autocfg", 328 | "cfg-if", 329 | "crossbeam-utils", 330 | "lazy_static", 331 | "maybe-uninit", 332 | "memoffset", 333 | "scopeguard", 334 | ] 335 | 336 | [[package]] 337 | name = "crossbeam-utils" 338 | version = "0.7.2" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 341 | dependencies = [ 342 | "autocfg", 343 | "cfg-if", 344 | "lazy_static", 345 | ] 346 | 347 | [[package]] 348 | name = "d3d12" 349 | version = "0.3.2" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "d0a60cceb22c7c53035f8980524fdc7f17cf49681a3c154e6757d30afbec6ec4" 352 | dependencies = [ 353 | "bitflags", 354 | "libloading", 355 | "winapi 0.3.9", 356 | ] 357 | 358 | [[package]] 359 | name = "deflate" 360 | version = "0.8.6" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" 363 | dependencies = [ 364 | "adler32", 365 | "byteorder", 366 | ] 367 | 368 | [[package]] 369 | name = "dispatch" 370 | version = "0.1.4" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "04e93ca78226c51902d7aa8c12c988338aadd9e85ed9c6be8aaac39192ff3605" 373 | 374 | [[package]] 375 | name = "dlib" 376 | version = "0.4.2" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76" 379 | dependencies = [ 380 | "libloading", 381 | ] 382 | 383 | [[package]] 384 | name = "downcast-rs" 385 | version = "1.2.0" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 388 | 389 | [[package]] 390 | name = "either" 391 | version = "1.6.1" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 394 | 395 | [[package]] 396 | name = "foreign-types" 397 | version = "0.3.2" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 400 | dependencies = [ 401 | "foreign-types-shared", 402 | ] 403 | 404 | [[package]] 405 | name = "foreign-types-shared" 406 | version = "0.1.1" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 409 | 410 | [[package]] 411 | name = "fuchsia-zircon" 412 | version = "0.3.3" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 415 | dependencies = [ 416 | "bitflags", 417 | "fuchsia-zircon-sys", 418 | ] 419 | 420 | [[package]] 421 | name = "fuchsia-zircon-sys" 422 | version = "0.3.3" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 425 | 426 | [[package]] 427 | name = "fxhash" 428 | version = "0.2.1" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 431 | dependencies = [ 432 | "byteorder", 433 | ] 434 | 435 | [[package]] 436 | name = "gfx-auxil" 437 | version = "0.6.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "7ec012a32036c6439180b688b15a24dc8a3fbdb3b1cd02eb55266201db4c1b0f" 440 | dependencies = [ 441 | "fxhash", 442 | "gfx-hal", 443 | "spirv_cross", 444 | ] 445 | 446 | [[package]] 447 | name = "gfx-backend-dx12" 448 | version = "0.6.3" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "0bfc194d9a1540073f181bae94087ffc9d84a5586b71962cd1b46b86e5a6d697" 451 | dependencies = [ 452 | "bitflags", 453 | "d3d12", 454 | "gfx-auxil", 455 | "gfx-hal", 456 | "log", 457 | "range-alloc", 458 | "raw-window-handle", 459 | "smallvec", 460 | "spirv_cross", 461 | "winapi 0.3.9", 462 | ] 463 | 464 | [[package]] 465 | name = "gfx-backend-metal" 466 | version = "0.6.2" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "d6b2b1e2510c8a283beac1e680cd152edc05d138c00dfabc0e3f636e143ffd66" 469 | dependencies = [ 470 | "arrayvec", 471 | "bitflags", 472 | "block", 473 | "cocoa-foundation", 474 | "copyless", 475 | "foreign-types", 476 | "gfx-auxil", 477 | "gfx-hal", 478 | "lazy_static", 479 | "log", 480 | "metal", 481 | "objc", 482 | "parking_lot 0.11.0", 483 | "range-alloc", 484 | "raw-window-handle", 485 | "smallvec", 486 | "spirv_cross", 487 | "storage-map", 488 | ] 489 | 490 | [[package]] 491 | name = "gfx-backend-vulkan" 492 | version = "0.6.1" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "a84bda4200a82e1912d575801e2bb76ae19c6256359afbc0adfbbaec02fcadc6" 495 | dependencies = [ 496 | "arrayvec", 497 | "ash", 498 | "byteorder", 499 | "core-graphics-types", 500 | "gfx-hal", 501 | "inplace_it", 502 | "lazy_static", 503 | "log", 504 | "objc", 505 | "raw-window-handle", 506 | "smallvec", 507 | "winapi 0.3.9", 508 | ] 509 | 510 | [[package]] 511 | name = "gfx-hal" 512 | version = "0.6.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "18d0754f5b7a43915fd7466883b2d1bb0800d7cc4609178d0b27bf143b9e5123" 515 | dependencies = [ 516 | "bitflags", 517 | "raw-window-handle", 518 | ] 519 | 520 | [[package]] 521 | name = "gfx-hal-tutorials" 522 | version = "0.1.0" 523 | dependencies = [ 524 | "bincode", 525 | "gfx-backend-dx12", 526 | "gfx-backend-metal", 527 | "gfx-backend-vulkan", 528 | "gfx-hal", 529 | "image", 530 | "serde", 531 | "shaderc", 532 | "winit", 533 | ] 534 | 535 | [[package]] 536 | name = "gif" 537 | version = "0.11.1" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "02efba560f227847cb41463a7395c514d127d4f74fff12ef0137fff1b84b96c4" 540 | dependencies = [ 541 | "color_quant", 542 | "weezl", 543 | ] 544 | 545 | [[package]] 546 | name = "hermit-abi" 547 | version = "0.1.16" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151" 550 | dependencies = [ 551 | "libc", 552 | ] 553 | 554 | [[package]] 555 | name = "image" 556 | version = "0.23.10" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "985fc06b1304d19c28d5c562ed78ef5316183f2b0053b46763a0b94862373c34" 559 | dependencies = [ 560 | "bytemuck", 561 | "byteorder", 562 | "gif", 563 | "jpeg-decoder", 564 | "num-iter", 565 | "num-rational", 566 | "num-traits", 567 | "png", 568 | "scoped_threadpool", 569 | "tiff", 570 | ] 571 | 572 | [[package]] 573 | name = "inplace_it" 574 | version = "0.3.2" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "dd01a2a73f2f399df96b22dc88ea687ef4d76226284e7531ae3c7ee1dc5cb534" 577 | 578 | [[package]] 579 | name = "instant" 580 | version = "0.1.7" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66" 583 | dependencies = [ 584 | "cfg-if", 585 | ] 586 | 587 | [[package]] 588 | name = "iovec" 589 | version = "0.1.4" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 592 | dependencies = [ 593 | "libc", 594 | ] 595 | 596 | [[package]] 597 | name = "jobserver" 598 | version = "0.1.21" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" 601 | dependencies = [ 602 | "libc", 603 | ] 604 | 605 | [[package]] 606 | name = "jpeg-decoder" 607 | version = "0.1.20" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "cc797adac5f083b8ff0ca6f6294a999393d76e197c36488e2ef732c4715f6fa3" 610 | dependencies = [ 611 | "byteorder", 612 | "rayon", 613 | ] 614 | 615 | [[package]] 616 | name = "js-sys" 617 | version = "0.3.45" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" 620 | dependencies = [ 621 | "wasm-bindgen", 622 | ] 623 | 624 | [[package]] 625 | name = "kernel32-sys" 626 | version = "0.2.2" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 629 | dependencies = [ 630 | "winapi 0.2.8", 631 | "winapi-build", 632 | ] 633 | 634 | [[package]] 635 | name = "lazy_static" 636 | version = "1.4.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 639 | 640 | [[package]] 641 | name = "lazycell" 642 | version = "1.3.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 645 | 646 | [[package]] 647 | name = "libc" 648 | version = "0.2.77" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" 651 | 652 | [[package]] 653 | name = "libloading" 654 | version = "0.6.3" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "2443d8f0478b16759158b2f66d525991a05491138bc05814ef52a250148ef4f9" 657 | dependencies = [ 658 | "cfg-if", 659 | "winapi 0.3.9", 660 | ] 661 | 662 | [[package]] 663 | name = "line_drawing" 664 | version = "0.7.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9" 667 | dependencies = [ 668 | "num-traits", 669 | ] 670 | 671 | [[package]] 672 | name = "lock_api" 673 | version = "0.3.4" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" 676 | dependencies = [ 677 | "scopeguard", 678 | ] 679 | 680 | [[package]] 681 | name = "lock_api" 682 | version = "0.4.1" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" 685 | dependencies = [ 686 | "scopeguard", 687 | ] 688 | 689 | [[package]] 690 | name = "log" 691 | version = "0.4.11" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 694 | dependencies = [ 695 | "cfg-if", 696 | ] 697 | 698 | [[package]] 699 | name = "lzw" 700 | version = "0.10.0" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" 703 | 704 | [[package]] 705 | name = "malloc_buf" 706 | version = "0.0.6" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 709 | dependencies = [ 710 | "libc", 711 | ] 712 | 713 | [[package]] 714 | name = "maybe-uninit" 715 | version = "2.0.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 718 | 719 | [[package]] 720 | name = "memmap" 721 | version = "0.7.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" 724 | dependencies = [ 725 | "libc", 726 | "winapi 0.3.9", 727 | ] 728 | 729 | [[package]] 730 | name = "memoffset" 731 | version = "0.5.6" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" 734 | dependencies = [ 735 | "autocfg", 736 | ] 737 | 738 | [[package]] 739 | name = "metal" 740 | version = "0.20.0" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "5c4e8a431536529327e28c9ba6992f2cb0c15d4222f0602a16e6d7695ff3bccf" 743 | dependencies = [ 744 | "bitflags", 745 | "block", 746 | "cocoa-foundation", 747 | "foreign-types", 748 | "log", 749 | "objc", 750 | ] 751 | 752 | [[package]] 753 | name = "miniz_oxide" 754 | version = "0.3.7" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 757 | dependencies = [ 758 | "adler32", 759 | ] 760 | 761 | [[package]] 762 | name = "mio" 763 | version = "0.6.22" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 766 | dependencies = [ 767 | "cfg-if", 768 | "fuchsia-zircon", 769 | "fuchsia-zircon-sys", 770 | "iovec", 771 | "kernel32-sys", 772 | "libc", 773 | "log", 774 | "miow", 775 | "net2", 776 | "slab", 777 | "winapi 0.2.8", 778 | ] 779 | 780 | [[package]] 781 | name = "mio-extras" 782 | version = "2.0.6" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" 785 | dependencies = [ 786 | "lazycell", 787 | "log", 788 | "mio", 789 | "slab", 790 | ] 791 | 792 | [[package]] 793 | name = "miow" 794 | version = "0.2.1" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 797 | dependencies = [ 798 | "kernel32-sys", 799 | "net2", 800 | "winapi 0.2.8", 801 | "ws2_32-sys", 802 | ] 803 | 804 | [[package]] 805 | name = "net2" 806 | version = "0.2.35" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" 809 | dependencies = [ 810 | "cfg-if", 811 | "libc", 812 | "winapi 0.3.9", 813 | ] 814 | 815 | [[package]] 816 | name = "nix" 817 | version = "0.14.1" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" 820 | dependencies = [ 821 | "bitflags", 822 | "cc", 823 | "cfg-if", 824 | "libc", 825 | "void", 826 | ] 827 | 828 | [[package]] 829 | name = "num-integer" 830 | version = "0.1.43" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 833 | dependencies = [ 834 | "autocfg", 835 | "num-traits", 836 | ] 837 | 838 | [[package]] 839 | name = "num-iter" 840 | version = "0.1.41" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" 843 | dependencies = [ 844 | "autocfg", 845 | "num-integer", 846 | "num-traits", 847 | ] 848 | 849 | [[package]] 850 | name = "num-rational" 851 | version = "0.3.0" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" 854 | dependencies = [ 855 | "autocfg", 856 | "num-integer", 857 | "num-traits", 858 | ] 859 | 860 | [[package]] 861 | name = "num-traits" 862 | version = "0.2.12" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 865 | dependencies = [ 866 | "autocfg", 867 | ] 868 | 869 | [[package]] 870 | name = "num_cpus" 871 | version = "1.13.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 874 | dependencies = [ 875 | "hermit-abi", 876 | "libc", 877 | ] 878 | 879 | [[package]] 880 | name = "objc" 881 | version = "0.2.7" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 884 | dependencies = [ 885 | "malloc_buf", 886 | "objc_exception", 887 | ] 888 | 889 | [[package]] 890 | name = "objc_exception" 891 | version = "0.1.2" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" 894 | dependencies = [ 895 | "cc", 896 | ] 897 | 898 | [[package]] 899 | name = "ordered-float" 900 | version = "1.1.0" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "3741934be594d77de1c8461ebcbbe866f585ea616a9753aa78f2bdc69f0e4579" 903 | dependencies = [ 904 | "num-traits", 905 | ] 906 | 907 | [[package]] 908 | name = "parking_lot" 909 | version = "0.10.2" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" 912 | dependencies = [ 913 | "lock_api 0.3.4", 914 | "parking_lot_core 0.7.2", 915 | ] 916 | 917 | [[package]] 918 | name = "parking_lot" 919 | version = "0.11.0" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" 922 | dependencies = [ 923 | "instant", 924 | "lock_api 0.4.1", 925 | "parking_lot_core 0.8.0", 926 | ] 927 | 928 | [[package]] 929 | name = "parking_lot_core" 930 | version = "0.7.2" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" 933 | dependencies = [ 934 | "cfg-if", 935 | "cloudabi 0.0.3", 936 | "libc", 937 | "redox_syscall", 938 | "smallvec", 939 | "winapi 0.3.9", 940 | ] 941 | 942 | [[package]] 943 | name = "parking_lot_core" 944 | version = "0.8.0" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" 947 | dependencies = [ 948 | "cfg-if", 949 | "cloudabi 0.1.0", 950 | "instant", 951 | "libc", 952 | "redox_syscall", 953 | "smallvec", 954 | "winapi 0.3.9", 955 | ] 956 | 957 | [[package]] 958 | name = "percent-encoding" 959 | version = "2.1.0" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 962 | 963 | [[package]] 964 | name = "pkg-config" 965 | version = "0.3.18" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" 968 | 969 | [[package]] 970 | name = "png" 971 | version = "0.16.7" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "dfe7f9f1c730833200b134370e1d5098964231af8450bce9b78ee3ab5278b970" 974 | dependencies = [ 975 | "bitflags", 976 | "crc32fast", 977 | "deflate", 978 | "miniz_oxide", 979 | ] 980 | 981 | [[package]] 982 | name = "proc-macro2" 983 | version = "0.4.30" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 986 | dependencies = [ 987 | "unicode-xid 0.1.0", 988 | ] 989 | 990 | [[package]] 991 | name = "proc-macro2" 992 | version = "1.0.23" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "51ef7cd2518ead700af67bf9d1a658d90b6037d77110fd9c0445429d0ba1c6c9" 995 | dependencies = [ 996 | "unicode-xid 0.2.1", 997 | ] 998 | 999 | [[package]] 1000 | name = "quote" 1001 | version = "0.6.13" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 1004 | dependencies = [ 1005 | "proc-macro2 0.4.30", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "quote" 1010 | version = "1.0.7" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 1013 | dependencies = [ 1014 | "proc-macro2 1.0.23", 1015 | ] 1016 | 1017 | [[package]] 1018 | name = "range-alloc" 1019 | version = "0.1.1" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "a871f1e45a3a3f0c73fb60343c811238bb5143a81642e27c2ac7aac27ff01a63" 1022 | 1023 | [[package]] 1024 | name = "raw-window-handle" 1025 | version = "0.3.3" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" 1028 | dependencies = [ 1029 | "libc", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "rayon" 1034 | version = "1.4.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270" 1037 | dependencies = [ 1038 | "autocfg", 1039 | "crossbeam-deque", 1040 | "either", 1041 | "rayon-core", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "rayon-core" 1046 | version = "1.8.1" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf" 1049 | dependencies = [ 1050 | "crossbeam-channel", 1051 | "crossbeam-deque", 1052 | "crossbeam-utils", 1053 | "lazy_static", 1054 | "num_cpus", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "redox_syscall" 1059 | version = "0.1.57" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 1062 | 1063 | [[package]] 1064 | name = "rusttype" 1065 | version = "0.7.9" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "310942406a39981bed7e12b09182a221a29e0990f3e7e0c971f131922ed135d5" 1068 | dependencies = [ 1069 | "rusttype 0.8.3", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "rusttype" 1074 | version = "0.8.3" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "9f61411055101f7b60ecf1041d87fb74205fb20b0c7a723f07ef39174cf6b4c0" 1077 | dependencies = [ 1078 | "approx", 1079 | "ordered-float", 1080 | "stb_truetype", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "same-file" 1085 | version = "1.0.6" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1088 | dependencies = [ 1089 | "winapi-util", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "scoped_threadpool" 1094 | version = "0.1.9" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 1097 | 1098 | [[package]] 1099 | name = "scopeguard" 1100 | version = "1.1.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1103 | 1104 | [[package]] 1105 | name = "serde" 1106 | version = "1.0.116" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" 1109 | dependencies = [ 1110 | "serde_derive", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "serde_derive" 1115 | version = "1.0.116" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" 1118 | dependencies = [ 1119 | "proc-macro2 1.0.23", 1120 | "quote 1.0.7", 1121 | "syn", 1122 | ] 1123 | 1124 | [[package]] 1125 | name = "shaderc" 1126 | version = "0.6.2" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "ed344938df2d7fa3cc6bfb4af0b578f00f9b389d5fe7be0250fa657c442a8281" 1129 | dependencies = [ 1130 | "libc", 1131 | "shaderc-sys", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "shaderc-sys" 1136 | version = "0.6.2" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "30075c712b08798cb2b5e54e4434970a4a3a3a3e838b0642590c74605d3cc528" 1139 | dependencies = [ 1140 | "cmake", 1141 | "libc", 1142 | ] 1143 | 1144 | [[package]] 1145 | name = "slab" 1146 | version = "0.4.2" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1149 | 1150 | [[package]] 1151 | name = "smallvec" 1152 | version = "1.4.2" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" 1155 | 1156 | [[package]] 1157 | name = "smithay-client-toolkit" 1158 | version = "0.6.6" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "421c8dc7acf5cb205b88160f8b4cc2c5cfabe210e43b2f80f009f4c1ef910f1d" 1161 | dependencies = [ 1162 | "andrew", 1163 | "bitflags", 1164 | "dlib", 1165 | "lazy_static", 1166 | "memmap", 1167 | "nix", 1168 | "wayland-client", 1169 | "wayland-protocols", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "spirv_cross" 1174 | version = "0.21.0" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "b631bd956108f3e34a4fb7e39621711ac15ce022bc567da2d953c6df13f00e00" 1177 | dependencies = [ 1178 | "cc", 1179 | "js-sys", 1180 | "wasm-bindgen", 1181 | ] 1182 | 1183 | [[package]] 1184 | name = "stb_truetype" 1185 | version = "0.3.1" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "f77b6b07e862c66a9f3e62a07588fee67cd90a9135a2b942409f195507b4fb51" 1188 | dependencies = [ 1189 | "byteorder", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "storage-map" 1194 | version = "0.3.0" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "418bb14643aa55a7841d5303f72cf512cfb323b8cc221d51580500a1ca75206c" 1197 | dependencies = [ 1198 | "lock_api 0.4.1", 1199 | ] 1200 | 1201 | [[package]] 1202 | name = "syn" 1203 | version = "1.0.42" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228" 1206 | dependencies = [ 1207 | "proc-macro2 1.0.23", 1208 | "quote 1.0.7", 1209 | "unicode-xid 0.2.1", 1210 | ] 1211 | 1212 | [[package]] 1213 | name = "tiff" 1214 | version = "0.5.0" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "3f3b8a87c4da944c3f27e5943289171ac71a6150a79ff6bacfff06d159dfff2f" 1217 | dependencies = [ 1218 | "byteorder", 1219 | "lzw", 1220 | "miniz_oxide", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "unicode-xid" 1225 | version = "0.1.0" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 1228 | 1229 | [[package]] 1230 | name = "unicode-xid" 1231 | version = "0.2.1" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1234 | 1235 | [[package]] 1236 | name = "void" 1237 | version = "1.0.2" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1240 | 1241 | [[package]] 1242 | name = "walkdir" 1243 | version = "2.3.1" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1246 | dependencies = [ 1247 | "same-file", 1248 | "winapi 0.3.9", 1249 | "winapi-util", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "wasm-bindgen" 1254 | version = "0.2.68" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" 1257 | dependencies = [ 1258 | "cfg-if", 1259 | "wasm-bindgen-macro", 1260 | ] 1261 | 1262 | [[package]] 1263 | name = "wasm-bindgen-backend" 1264 | version = "0.2.68" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" 1267 | dependencies = [ 1268 | "bumpalo", 1269 | "lazy_static", 1270 | "log", 1271 | "proc-macro2 1.0.23", 1272 | "quote 1.0.7", 1273 | "syn", 1274 | "wasm-bindgen-shared", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "wasm-bindgen-macro" 1279 | version = "0.2.68" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" 1282 | dependencies = [ 1283 | "quote 1.0.7", 1284 | "wasm-bindgen-macro-support", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "wasm-bindgen-macro-support" 1289 | version = "0.2.68" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" 1292 | dependencies = [ 1293 | "proc-macro2 1.0.23", 1294 | "quote 1.0.7", 1295 | "syn", 1296 | "wasm-bindgen-backend", 1297 | "wasm-bindgen-shared", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "wasm-bindgen-shared" 1302 | version = "0.2.68" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" 1305 | 1306 | [[package]] 1307 | name = "wayland-client" 1308 | version = "0.23.6" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "af1080ebe0efabcf12aef2132152f616038f2d7dcbbccf7b2d8c5270fe14bcda" 1311 | dependencies = [ 1312 | "bitflags", 1313 | "calloop", 1314 | "downcast-rs", 1315 | "libc", 1316 | "mio", 1317 | "nix", 1318 | "wayland-commons", 1319 | "wayland-scanner", 1320 | "wayland-sys", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "wayland-commons" 1325 | version = "0.23.6" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "bb66b0d1a27c39bbce712b6372131c6e25149f03ffb0cd017cf8f7de8d66dbdb" 1328 | dependencies = [ 1329 | "nix", 1330 | "wayland-sys", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "wayland-protocols" 1335 | version = "0.23.6" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "6cc286643656742777d55dc8e70d144fa4699e426ca8e9d4ef454f4bf15ffcf9" 1338 | dependencies = [ 1339 | "bitflags", 1340 | "wayland-client", 1341 | "wayland-commons", 1342 | "wayland-scanner", 1343 | ] 1344 | 1345 | [[package]] 1346 | name = "wayland-scanner" 1347 | version = "0.23.6" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "93b02247366f395b9258054f964fe293ddd019c3237afba9be2ccbe9e1651c3d" 1350 | dependencies = [ 1351 | "proc-macro2 0.4.30", 1352 | "quote 0.6.13", 1353 | "xml-rs", 1354 | ] 1355 | 1356 | [[package]] 1357 | name = "wayland-sys" 1358 | version = "0.23.6" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "d94e89a86e6d6d7c7c9b19ebf48a03afaac4af6bc22ae570e9a24124b75358f4" 1361 | dependencies = [ 1362 | "dlib", 1363 | "lazy_static", 1364 | ] 1365 | 1366 | [[package]] 1367 | name = "weezl" 1368 | version = "0.1.0" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "a3d2f24b6c3aa92fb33279566dbebf1cbe66b03a73f09aa69cf8cf14d2f9feb9" 1371 | 1372 | [[package]] 1373 | name = "winapi" 1374 | version = "0.2.8" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1377 | 1378 | [[package]] 1379 | name = "winapi" 1380 | version = "0.3.9" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1383 | dependencies = [ 1384 | "winapi-i686-pc-windows-gnu", 1385 | "winapi-x86_64-pc-windows-gnu", 1386 | ] 1387 | 1388 | [[package]] 1389 | name = "winapi-build" 1390 | version = "0.1.1" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1393 | 1394 | [[package]] 1395 | name = "winapi-i686-pc-windows-gnu" 1396 | version = "0.4.0" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1399 | 1400 | [[package]] 1401 | name = "winapi-util" 1402 | version = "0.1.5" 1403 | source = "registry+https://github.com/rust-lang/crates.io-index" 1404 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1405 | dependencies = [ 1406 | "winapi 0.3.9", 1407 | ] 1408 | 1409 | [[package]] 1410 | name = "winapi-x86_64-pc-windows-gnu" 1411 | version = "0.4.0" 1412 | source = "registry+https://github.com/rust-lang/crates.io-index" 1413 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1414 | 1415 | [[package]] 1416 | name = "winit" 1417 | version = "0.20.0" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "3ba128780050481f453bec2a115b916dbc6ae79c303dee9bad8b9080bdccd4f5" 1420 | dependencies = [ 1421 | "android_glue", 1422 | "bitflags", 1423 | "cocoa", 1424 | "core-foundation 0.6.4", 1425 | "core-graphics 0.17.3", 1426 | "core-video-sys", 1427 | "dispatch", 1428 | "instant", 1429 | "lazy_static", 1430 | "libc", 1431 | "log", 1432 | "mio", 1433 | "mio-extras", 1434 | "objc", 1435 | "parking_lot 0.10.2", 1436 | "percent-encoding", 1437 | "raw-window-handle", 1438 | "smithay-client-toolkit", 1439 | "wayland-client", 1440 | "winapi 0.3.9", 1441 | "x11-dl", 1442 | ] 1443 | 1444 | [[package]] 1445 | name = "ws2_32-sys" 1446 | version = "0.2.1" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1449 | dependencies = [ 1450 | "winapi 0.2.8", 1451 | "winapi-build", 1452 | ] 1453 | 1454 | [[package]] 1455 | name = "x11-dl" 1456 | version = "2.18.5" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8" 1459 | dependencies = [ 1460 | "lazy_static", 1461 | "libc", 1462 | "maybe-uninit", 1463 | "pkg-config", 1464 | ] 1465 | 1466 | [[package]] 1467 | name = "xdg" 1468 | version = "2.2.0" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" 1471 | 1472 | [[package]] 1473 | name = "xml-rs" 1474 | version = "0.8.3" 1475 | source = "registry+https://github.com/rust-lang/crates.io-index" 1476 | checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" 1477 | --------------------------------------------------------------------------------