├── .github ├── FUNDING.yml └── workflows │ └── tests.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── demo_app.rs ├── minimal.rs ├── multisample.rs ├── paint_callback.rs ├── subpass.rs └── wholesome │ ├── assets │ ├── doge2.png │ └── tree.png │ ├── frame_system.rs │ ├── main.rs │ ├── renderer.rs │ ├── time_info.rs │ └── triangle_draw_system.rs ├── run_all_examples.ps1 ├── run_all_examples.sh ├── run_checks.ps1 ├── run_checks.sh ├── rustfmt.toml └── src ├── integration.rs ├── lib.rs ├── renderer.rs └── utils.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [hakolao] 2 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | lint: 14 | name: Lint 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: nightly 21 | override: true 22 | - name: Rust Lint Cache 23 | uses: Swatinem/rust-cache@v2 24 | with: 25 | shared-key: "${{ runner.os }}-rust-lint" 26 | - run: rustup component add rustfmt 27 | - name: check rustfmt 28 | run: cargo fmt -- --check --color always 29 | - run: rustup component add clippy 30 | - run: cargo fetch 31 | - name: cargo clippy 32 | run: cargo clippy --all-targets -- -D warnings 33 | 34 | windows_stable: 35 | runs-on: windows-latest 36 | steps: 37 | - name: Ninja Install 38 | run: pip install ninja 39 | - uses: actions/checkout@v3 40 | - name: Rust Windows Cache 41 | uses: Swatinem/rust-cache@v2 42 | with: 43 | shared-key: "${{ runner.os }}-rust-windows" 44 | - name: Build 45 | run: cargo build --verbose 46 | - name: Run tests 47 | run: cargo test --verbose 48 | linux_stable: 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v3 52 | - name: Rust Linux Cache 53 | uses: Swatinem/rust-cache@v2 54 | with: 55 | shared-key: "${{ runner.os }}-rust-linux" 56 | - name: Build 57 | run: cargo build --verbose 58 | - name: Run tests 59 | run: cargo test --verbose 60 | macos_stable: 61 | runs-on: macos-latest 62 | steps: 63 | - uses: actions/checkout@v3 64 | - name: Rust Macos Cache 65 | uses: Swatinem/rust-cache@v2 66 | with: 67 | shared-key: "${{ runner.os }}-rust-macos" 68 | - name: Build 69 | run: cargo build --verbose 70 | - name: Run tests 71 | run: cargo test --verbose 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui_winit_vulkano" 3 | version = "0.28.0" 4 | authors = ["hakolao "] 5 | edition = "2021" 6 | description = "Egui immediate mode gui integration with winit and Vulkano" 7 | homepage = "https://github.com/hakolao/egui_winit_vulkano" 8 | license = "Apache-2.0" 9 | readme = "README.md" 10 | repository = "https://github.com/hakolao/egui_winit_vulkano" 11 | categories = ["gui", "game-development"] 12 | keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] 13 | 14 | [features] 15 | default = ["clipboard", "links", "wayland", "x11", "image"] 16 | links = ["egui-winit/links"] 17 | clipboard = ["egui-winit/clipboard"] 18 | wayland = ["winit/wayland", "winit/wayland-dlopen", "egui-winit/wayland"] 19 | x11 = ["winit/x11", "egui-winit/x11"] 20 | 21 | [dependencies] 22 | ahash = "0.8" 23 | egui-winit = { version = "0.31", default-features = false } 24 | egui = "0.31" 25 | image = { version = "0.25", optional = true } 26 | winit = { version = "0.30", default-features = false, features = ["rwh_06"] } 27 | vulkano = { version = "0.35" } 28 | vulkano-shaders = "0.35" 29 | 30 | [dev-dependencies] 31 | cgmath = "0.18.0" 32 | egui_demo_lib = "0.31.0" 33 | vulkano-util = { version = "0.35" } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egui_winit_vulkano 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/egui_winit_vulkano.svg)](https://crates.io/crates/egui_winit_vulkano) 4 | ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) 5 | ![CI](https://github.com/hakolao/egui_winit_vulkano/workflows/CI/badge.svg) 6 | 7 | This is an [egui](https://github.com/emilk/egui) integration for 8 | [winit](https://github.com/rust-windowing/winit) and [vulkano](https://github.com/vulkano-rs/vulkano). 9 | 10 | You'll need a Vulkano target image as an input to which the UI will be painted. 11 | The aim of this is to allow a simple enough API to separate UI nicely out of your renderer and make it easy to build your immediate mode UI with Egui. 12 | 13 | # Usage 14 | 1. Create your own renderer with Vulkano, and allow access to Vulkano's gfx queue `Arc` and Vulkano's winit surface `Arc>` 15 | 2. Create Gui integration with the surface & gfx queue 16 | 17 | ```rust 18 | // Has its own renderpass. Modify GuiConfig to determine image clear behavior etc. 19 | let mut gui = Gui::new(&event_loop, renderer.surface(), renderer.queue(), renderer.swapchain_format(), GuiConfig::default()); 20 | // Or with subpass. This means that you must create the renderpass yourself. Egui subpass will then draw on your 21 | // image. 22 | let mut gui = Gui::new_with_subpass(&event_loop, renderer.surface(), renderer.queue(), renderer.swapchain_format(), subpass, GuiConfig::default()); 23 | ``` 24 | 25 | 3. Inside your event loop, update `gui` integration with `WindowEvent` 26 | 27 | ```rust 28 | gui.update(&event); 29 | ``` 30 | 31 | 4. Fill immediate mode UI through the integration in `Event::RedrawRequested` before you render 32 | ```rust 33 | gui.immediate_ui(|gui| { 34 | let ctx = gui.context(); 35 | // Fill egui UI layout here 36 | }); 37 | 38 | // Or 39 | 40 | gui.begin_frame(); 41 | // fill egui layout... 42 | 43 | 44 | // And when you render with `gui.draw_on_image(..)`, this will finish the egui frame 45 | ``` 46 | 5. Render gui via your renderer on any image or most likely on your swapchain images: 47 | ```rust 48 | // Acquire swapchain future 49 | let before_future = renderer.acquire().unwrap(); 50 | // Render gui by passing the acquire future (or any) and render target image (swapchain image view) 51 | let after_future = gui.draw_on_image(before_future, renderer.swapchain_image_view()); 52 | // Present swapchain 53 | renderer.present(after_future, true); 54 | // ---------------------------------- 55 | // Or if you created the integration with subpass 56 | let cb = gui.draw_on_subpass_image(framebuffer_dimensions); 57 | draw_pass.execute(cb); 58 | ``` 59 | Note that Egui strongly prefers UNORM render targets, and passing an sRGB color space image is considered an error. See [The correct color space](#the-correct-color-space) for more details on how to deal with it. 60 | 61 | See the examples directory for better usage guidance. 62 | 63 | Remember, on Linux, you need to install following to run Egui 64 | ```bash 65 | sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev 66 | ``` 67 | 68 | # Examples 69 | 70 | ```sh 71 | ./run_all_examples.sh 72 | ``` 73 | 74 | # The correct color space 75 | The key takeaway is that egui assumes any math in the shaders as well as [alpha blending is done using colors in sRGB color space](https://github.com/emilk/egui/pull/2071). Meanwhile, Vulkan assumes that one always works with colors in linear space, and will automatically convert sRGB textures to linear colors when using them in shaders. This is what we need to work around. 76 |
77 | Longer explainer 78 | 79 | ### What usually happens 80 | 81 | The GPU converts all colors to linear values. sRGB textures are mainly an implementation detail that the GPU can use to store colors for human vision with fewer bits. 82 | 83 | ```mermaid 84 | flowchart LR 85 | A[Shader A] -->|output linear colors| BB(UNORM Image) 86 | BB -->|reads linear colors| B[Shader B] 87 | 88 | C[Shader A] -->|GPU converts to sRGB| DD(sRGB Image) 89 | DD -->|GPU converts to linear colors| D[Shader B] 90 | ``` 91 | 92 | Meanwhile, Egui expects that the input and output colors are in sRGB color space. Very unusual, but happens to make sense for them. 93 | 94 | So with Egui, we can use a we tricks. 95 | 96 | We can always (ab)use a UNORM texture to store values without any conversions. 97 | ```mermaid 98 | flowchart LR 99 | A[EGui] -->|output sRGB colors \n no conversion| BB(UNORM Image) 100 | BB -->|directly reads colors \n no conversion| B[Your shader gets sRGB values] 101 | ``` 102 | Your shader can then take the sRGB values and manually convert them to linear values. 103 | 104 | The alternative, and better trick is using an image with multiple views. 105 | ```mermaid 106 | flowchart LR 107 | I(Image) 108 | I1(UNORM Image View) 109 | I2(sRGB Image View) 110 | 111 | A[EGui] -->|output sRGB colors \n no conversion| I1 112 | 113 | I2 -->|converts sRGB to linear| B[Your shader gets linear values] 114 | 115 | I --- I1 116 | I --- I2 117 | ``` 118 | 119 | The same two techniques can also be applied to swapchains. 120 | 121 |
122 | 123 | 1. The simplest approach is to create the swapchain using a UNORM image format, as is done in most examples. The display engine will [read the swapchain image, and interpret it as sRGB colors](https://stackoverflow.com/a/66401423/3492994). This happens regardless of the swapchain format. When using this approach, Egui works as expected. However, all of your shaders that draw to the swapchain will need to manually convert pixels to the sRGB format. 124 | 2. You render into a UNORM image you allocated, and then copy that image onto your swapchain image using compute shaders or a fullscreen triangle. Your shader needs to read the UNORM image, which if you remember contains values in sRGB color space, manually convert it from sRGB to linear color space, and then write it to your sRGB swapchain image. This conversion is also why you cannot just blit the image, but need to use a shader in between. 125 | 3. You again render into an image you allocated with the `VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT` flag (the format may be UNORM or SRGB). This flag allows you to create image views with [compatible but differing formats](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#formats-compatibility-classes). Create two image views, one viewing your data as UNORM and one as sRGB (e.g. `VK_FORMAT_R8G8B8A8_UNORM` and `VK_FORMAT_R8G8B8A8_SRGB`) by modifying `ImageViewCreateInfo::format` parameter from the default value. Render your Egui into the UNORM image view, then you may blit it from the sRGB image view onto your framebuffer. 126 | 4. If your device supports [`VK_KHR_swapchain_mutable_format`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_swapchain_mutable_format.html), which AMD, Nvidia and Intel do on both Windows and Linux, you may get around having to allocate an extra image and blit it completely. Enabling this extension and passing the `VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR` flag when creating your swapchain, which should be in sRGB format, allows you to create image views of your swapchain with differing formats. So just like in 2, you may now create two image views, one as sRGB to use in your 3D rendering, and one as UNORM to use in a following Egui pass. But as you are rendering directly into your swapchain, there is no need for copying or blitting. 127 | 128 | For desktop platforms, we would recommend approach 4 as it's supported by all major platforms and vendors, the most convenient to use, and saves both memory and memory bandwidth. If you intend to go for maximum compatibility, we recommend implementing approach 3 as it does not require any extra features or extensions, while still supporting approach 4 as it should be trivial to implement. Approach 2 is only interesting if you can combine the in-shader copy with some other post-processing shaders you would have to run anyway. 129 | 130 | If you so wish, you may still draw to an image view in sRGB format, if you can accept discolorations when elements are alpha blended together. Doing so requires enabling `GuiConfig::allow_srgb_render_target`, as otherwise it is an error to draw to an sRGB image view. Normally, you would expect that drawing to the wrong color space will cause the entire UI to be discolored. But enabling that option also slightly changes the shader we are using for drawing UI elements, to only have discolorations in alpha blended areas instead of the entire image. 131 | 132 | # Notes 133 | This integration would not have been possible without the examples from [vulkano-examples](https://github.com/vulkano-rs/vulkano/tree/master/examples/src/bin) 134 | or [egui_winit_ash_vk_mem](https://github.com/MatchaChoco010/egui_winit_ash_vk_mem). 135 | -------------------------------------------------------------------------------- /examples/demo_app.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | use egui_demo_lib::{ColorTest, DemoWindows}; 11 | use egui_winit_vulkano::{Gui, GuiConfig}; 12 | use vulkano_util::{ 13 | context::{VulkanoConfig, VulkanoContext}, 14 | window::{VulkanoWindows, WindowDescriptor}, 15 | }; 16 | use winit::{ 17 | application::ApplicationHandler, error::EventLoopError, event::WindowEvent, 18 | event_loop::EventLoop, window::WindowId, 19 | }; 20 | 21 | // Simply create egui demo apps to test everything works correctly. 22 | // Creates two windows with different color formats for their swapchain. 23 | 24 | pub struct App { 25 | context: VulkanoContext, 26 | windows: VulkanoWindows, 27 | window1: Option, 28 | window2: Option, 29 | } 30 | 31 | pub struct Window { 32 | id: WindowId, 33 | gui: Gui, 34 | demo_app: DemoWindows, 35 | egui_test: ColorTest, 36 | } 37 | 38 | impl Default for App { 39 | fn default() -> Self { 40 | // Vulkano context 41 | let context = VulkanoContext::new(VulkanoConfig::default()); 42 | // Vulkano windows (create one) 43 | let windows = VulkanoWindows::default(); 44 | 45 | Self { context, windows, window1: None, window2: None } 46 | } 47 | } 48 | 49 | impl ApplicationHandler for App { 50 | fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { 51 | // Display the demo application that ships with egui. 52 | let demo_app1 = DemoWindows::default(); 53 | let demo_app2 = DemoWindows::default(); 54 | let egui_test1 = ColorTest::default(); 55 | let egui_test2 = ColorTest::default(); 56 | 57 | let window1 = self.windows.create_window( 58 | event_loop, 59 | &self.context, 60 | &WindowDescriptor { 61 | title: String::from("egui_winit_vulkano SRGB"), 62 | ..WindowDescriptor::default() 63 | }, 64 | |ci| { 65 | ci.image_format = vulkano::format::Format::B8G8R8A8_SRGB; 66 | ci.min_image_count = ci.min_image_count.max(2); 67 | }, 68 | ); 69 | 70 | let window2 = self.windows.create_window( 71 | event_loop, 72 | &self.context, 73 | &WindowDescriptor { 74 | title: String::from("egui_winit_vulkano UNORM"), 75 | ..WindowDescriptor::default() 76 | }, 77 | |ci| { 78 | ci.image_format = vulkano::format::Format::B8G8R8A8_UNORM; 79 | ci.min_image_count = ci.min_image_count.max(2); 80 | }, 81 | ); 82 | 83 | // Create gui as main render pass (no overlay means it clears the image each frame) 84 | let gui1 = { 85 | let renderer = self.windows.get_renderer_mut(window1).unwrap(); 86 | Gui::new( 87 | event_loop, 88 | renderer.surface(), 89 | renderer.graphics_queue(), 90 | renderer.swapchain_format(), 91 | GuiConfig { allow_srgb_render_target: true, ..GuiConfig::default() }, 92 | ) 93 | }; 94 | 95 | let gui2 = { 96 | let renderer = self.windows.get_renderer_mut(window2).unwrap(); 97 | Gui::new( 98 | event_loop, 99 | renderer.surface(), 100 | renderer.graphics_queue(), 101 | renderer.swapchain_format(), 102 | GuiConfig::default(), 103 | ) 104 | }; 105 | 106 | self.window1 = 107 | Some(Window { id: window1, gui: gui1, demo_app: demo_app1, egui_test: egui_test1 }); 108 | 109 | self.window2 = 110 | Some(Window { id: window2, gui: gui2, demo_app: demo_app2, egui_test: egui_test2 }); 111 | } 112 | 113 | fn window_event( 114 | &mut self, 115 | event_loop: &winit::event_loop::ActiveEventLoop, 116 | window_id: winit::window::WindowId, 117 | event: WindowEvent, 118 | ) { 119 | let renderer = self.windows.get_renderer_mut(window_id).unwrap(); 120 | 121 | let w1 = self.window1.as_mut().unwrap(); 122 | let w2 = self.window2.as_mut().unwrap(); 123 | 124 | // Quick and ugly... 125 | let gui = if window_id == w1.id { &mut w1.gui } else { &mut w2.gui }; 126 | let demo_app = if window_id == w1.id { &mut w1.demo_app } else { &mut w2.demo_app }; 127 | let egui_test = if window_id == w1.id { &mut w1.egui_test } else { &mut w2.egui_test }; 128 | 129 | // Update Egui integration so the UI works! 130 | let _pass_events_to_game = !gui.update(&event); 131 | match event { 132 | WindowEvent::Resized(_) => { 133 | renderer.resize(); 134 | } 135 | WindowEvent::ScaleFactorChanged { .. } => { 136 | renderer.resize(); 137 | } 138 | WindowEvent::CloseRequested => { 139 | event_loop.exit(); 140 | } 141 | WindowEvent::RedrawRequested => { 142 | // Set immediate UI in redraw here 143 | gui.immediate_ui(|gui| { 144 | let ctx = gui.context(); 145 | demo_app.ui(&ctx); 146 | 147 | egui::Window::new("Colors").vscroll(true).show(&ctx, |ui| { 148 | egui_test.ui(ui); 149 | }); 150 | }); 151 | // Alternatively you could 152 | // gui.begin_frame(); 153 | // let ctx = gui.context(); 154 | // demo_app.ui(&ctx); 155 | 156 | // Render UI 157 | // Acquire swapchain future 158 | match renderer.acquire(Some(std::time::Duration::from_millis(10)), |_| {}) { 159 | Ok(future) => { 160 | let after_future = 161 | gui.draw_on_image(future, renderer.swapchain_image_view()); 162 | // Present swapchain 163 | renderer.present(after_future, true); 164 | } 165 | Err(vulkano::VulkanError::OutOfDate) => { 166 | renderer.resize(); 167 | } 168 | Err(e) => panic!("Failed to acquire swapchain future: {}", e), 169 | }; 170 | } 171 | _ => (), 172 | } 173 | } 174 | 175 | fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { 176 | for (_, renderer) in self.windows.iter_mut() { 177 | renderer.window().request_redraw(); 178 | } 179 | } 180 | } 181 | 182 | pub fn main() -> Result<(), EventLoopError> { 183 | // Winit event loop 184 | let event_loop = EventLoop::new().unwrap(); 185 | 186 | let mut app = App::default(); 187 | 188 | event_loop.run_app(&mut app) 189 | } 190 | -------------------------------------------------------------------------------- /examples/minimal.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | #![allow(clippy::eq_op)] 11 | 12 | use egui::{ScrollArea, TextEdit, TextStyle}; 13 | use egui_winit_vulkano::{Gui, GuiConfig}; 14 | use vulkano_util::{ 15 | context::{VulkanoConfig, VulkanoContext}, 16 | window::{VulkanoWindows, WindowDescriptor}, 17 | }; 18 | use winit::{ 19 | application::ApplicationHandler, error::EventLoopError, event::WindowEvent, 20 | event_loop::EventLoop, 21 | }; 22 | 23 | fn sized_text(ui: &mut egui::Ui, text: impl Into, size: f32) { 24 | ui.label(egui::RichText::new(text).size(size)); 25 | } 26 | 27 | pub struct App { 28 | context: VulkanoContext, 29 | windows: VulkanoWindows, 30 | code: String, 31 | gui: Option, 32 | } 33 | 34 | impl Default for App { 35 | fn default() -> Self { 36 | // Vulkano context 37 | let context = VulkanoContext::new(VulkanoConfig::default()); 38 | 39 | // Vulkano windows 40 | let windows = VulkanoWindows::default(); 41 | 42 | let code = CODE.to_owned(); 43 | 44 | Self { context, windows, code, gui: None } 45 | } 46 | } 47 | 48 | impl ApplicationHandler for App { 49 | fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { 50 | self.windows.create_window(event_loop, &self.context, &WindowDescriptor::default(), |ci| { 51 | ci.image_format = vulkano::format::Format::B8G8R8A8_UNORM; 52 | ci.min_image_count = ci.min_image_count.max(2); 53 | }); 54 | 55 | // Create gui as main render pass (no overlay means it clears the image each frame) 56 | self.gui = Some({ 57 | let renderer = self.windows.get_primary_renderer_mut().unwrap(); 58 | Gui::new( 59 | event_loop, 60 | renderer.surface(), 61 | renderer.graphics_queue(), 62 | renderer.swapchain_format(), 63 | GuiConfig::default(), 64 | ) 65 | }); 66 | } 67 | 68 | fn window_event( 69 | &mut self, 70 | event_loop: &winit::event_loop::ActiveEventLoop, 71 | window_id: winit::window::WindowId, 72 | event: WindowEvent, 73 | ) { 74 | let renderer = self.windows.get_renderer_mut(window_id).unwrap(); 75 | 76 | let gui = self.gui.as_mut().unwrap(); 77 | 78 | // Update Egui integration so the UI works! 79 | let _pass_events_to_game = !gui.update(&event); 80 | match event { 81 | WindowEvent::Resized(_) => { 82 | renderer.resize(); 83 | } 84 | WindowEvent::ScaleFactorChanged { .. } => { 85 | renderer.resize(); 86 | } 87 | WindowEvent::CloseRequested => { 88 | event_loop.exit(); 89 | } 90 | WindowEvent::RedrawRequested => { 91 | // Set immediate UI in redraw here 92 | // Set immediate UI in redraw here 93 | gui.immediate_ui(|gui| { 94 | let ctx = gui.context(); 95 | egui::CentralPanel::default().show(&ctx, |ui| { 96 | ui.vertical_centered(|ui| { 97 | ui.add(egui::widgets::Label::new("Hi there!")); 98 | sized_text(ui, "Rich Text", 32.0); 99 | }); 100 | ui.separator(); 101 | ui.columns(2, |columns| { 102 | ScrollArea::vertical().id_salt("source").show(&mut columns[0], |ui| { 103 | ui.add( 104 | TextEdit::multiline(&mut self.code).font(TextStyle::Monospace), 105 | ); 106 | }); 107 | ScrollArea::vertical().id_salt("rendered").show( 108 | &mut columns[1], 109 | |ui| { 110 | egui_demo_lib::easy_mark::easy_mark(ui, &self.code); 111 | }, 112 | ); 113 | }); 114 | }); 115 | }); 116 | // Render UI 117 | // Acquire swapchain future 118 | match renderer.acquire(Some(std::time::Duration::from_millis(10)), |_| {}) { 119 | Ok(future) => { 120 | // Render gui 121 | let after_future = self 122 | .gui 123 | .as_mut() 124 | .unwrap() 125 | .draw_on_image(future, renderer.swapchain_image_view()); 126 | // Present swapchain 127 | renderer.present(after_future, true); 128 | } 129 | Err(vulkano::VulkanError::OutOfDate) => { 130 | renderer.resize(); 131 | } 132 | Err(e) => panic!("Failed to acquire swapchain future: {}", e), 133 | }; 134 | } 135 | _ => (), 136 | } 137 | } 138 | fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { 139 | let renderer = self.windows.get_primary_renderer_mut().unwrap(); 140 | renderer.window().request_redraw(); 141 | } 142 | } 143 | 144 | pub fn main() -> Result<(), EventLoopError> { 145 | // Winit event loop 146 | let event_loop = EventLoop::new().unwrap(); 147 | 148 | let mut app = App::default(); 149 | 150 | event_loop.run_app(&mut app) 151 | } 152 | 153 | const CODE: &str = r" 154 | # Some markup 155 | ``` 156 | let mut gui = Gui::new(&event_loop, renderer.surface(), None, renderer.queue(), SampleCount::Sample1); 157 | ``` 158 | "; 159 | -------------------------------------------------------------------------------- /examples/multisample.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | #![allow(clippy::eq_op)] 11 | 12 | use std::sync::Arc; 13 | 14 | use egui::{epaint::Shadow, vec2, Align, Align2, Color32, CornerRadius, Frame, Margin, Window}; 15 | use egui_winit_vulkano::{Gui, GuiConfig}; 16 | use vulkano::{ 17 | buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, 18 | command_buffer::{ 19 | allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo}, 20 | AutoCommandBufferBuilder, CommandBufferInheritanceInfo, CommandBufferUsage, 21 | RenderPassBeginInfo, SubpassBeginInfo, SubpassContents, 22 | }, 23 | device::{Device, Queue}, 24 | format::Format, 25 | image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage, SampleCount}, 26 | memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, 27 | pipeline::{ 28 | graphics::{ 29 | color_blend::{ColorBlendAttachmentState, ColorBlendState}, 30 | input_assembly::InputAssemblyState, 31 | multisample::MultisampleState, 32 | rasterization::RasterizationState, 33 | vertex_input::{Vertex, VertexDefinition}, 34 | viewport::{Viewport, ViewportState}, 35 | GraphicsPipelineCreateInfo, 36 | }, 37 | layout::PipelineDescriptorSetLayoutCreateInfo, 38 | DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, 39 | }, 40 | render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, 41 | sync::GpuFuture, 42 | }; 43 | use vulkano_util::{ 44 | context::{VulkanoConfig, VulkanoContext}, 45 | window::{VulkanoWindows, WindowDescriptor}, 46 | }; 47 | use winit::{ 48 | application::ApplicationHandler, error::EventLoopError, event::WindowEvent, 49 | event_loop::EventLoop, 50 | }; 51 | 52 | pub struct App { 53 | context: VulkanoContext, 54 | windows: VulkanoWindows, 55 | pipeline: Option, 56 | gui: Option, 57 | } 58 | 59 | impl Default for App { 60 | fn default() -> Self { 61 | // Vulkano context 62 | let context = VulkanoContext::new(VulkanoConfig::default()); 63 | 64 | // Vulkano windows 65 | let windows = VulkanoWindows::default(); 66 | 67 | Self { context, windows, pipeline: None, gui: None } 68 | } 69 | } 70 | 71 | impl ApplicationHandler for App { 72 | fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { 73 | self.windows.create_window(event_loop, &self.context, &WindowDescriptor::default(), |ci| { 74 | ci.image_format = vulkano::format::Format::B8G8R8A8_UNORM; 75 | ci.image_usage = ImageUsage::TRANSFER_DST | ci.image_usage; 76 | ci.min_image_count = ci.min_image_count.max(2); 77 | }); 78 | 79 | // Create out gui pipeline 80 | let pipeline = MSAAPipeline::new( 81 | self.context.graphics_queue().clone(), 82 | self.windows.get_primary_renderer_mut().unwrap().swapchain_format(), 83 | self.context.memory_allocator(), 84 | SampleCount::Sample4, 85 | ); 86 | 87 | // Create gui subpass 88 | self.gui = Some(Gui::new_with_subpass( 89 | event_loop, 90 | self.windows.get_primary_renderer_mut().unwrap().surface(), 91 | self.windows.get_primary_renderer_mut().unwrap().graphics_queue(), 92 | pipeline.gui_pass(), 93 | self.windows.get_primary_renderer_mut().unwrap().swapchain_format(), 94 | GuiConfig { 95 | // Must match your pipeline's sample count 96 | samples: SampleCount::Sample4, 97 | ..Default::default() 98 | }, 99 | )); 100 | 101 | self.pipeline = Some(pipeline); 102 | } 103 | 104 | fn window_event( 105 | &mut self, 106 | event_loop: &winit::event_loop::ActiveEventLoop, 107 | window_id: winit::window::WindowId, 108 | event: WindowEvent, 109 | ) { 110 | let renderer = self.windows.get_renderer_mut(window_id).unwrap(); 111 | 112 | let gui = self.gui.as_mut().unwrap(); 113 | 114 | // Update Egui integration so the UI works! 115 | let _pass_events_to_game = !gui.update(&event); 116 | match event { 117 | WindowEvent::Resized(_) => { 118 | renderer.resize(); 119 | } 120 | WindowEvent::ScaleFactorChanged { .. } => { 121 | renderer.resize(); 122 | } 123 | WindowEvent::CloseRequested => event_loop.exit(), 124 | WindowEvent::RedrawRequested => { 125 | // Set immediate UI in redraw here 126 | gui.immediate_ui(|gui| { 127 | let ctx = gui.context(); 128 | Window::new("Transparent Window") 129 | .anchor(Align2([Align::RIGHT, Align::TOP]), vec2(-545.0, 500.0)) 130 | .resizable(false) 131 | .default_width(300.0) 132 | .frame( 133 | Frame::NONE 134 | .fill(Color32::from_white_alpha(125)) 135 | .shadow(Shadow { 136 | spread: 8, 137 | blur: 10, 138 | color: Color32::from_black_alpha(125), 139 | ..Default::default() 140 | }) 141 | .corner_radius(CornerRadius::same(5)) 142 | .inner_margin(Margin::same(10)), 143 | ) 144 | .show(&ctx, |ui| { 145 | ui.colored_label(Color32::BLACK, "Content :)"); 146 | }); 147 | }); 148 | // Render 149 | // Acquire swapchain future 150 | match renderer.acquire(Some(std::time::Duration::from_millis(10)), |_| {}) { 151 | Ok(future) => { 152 | // Render 153 | let after_future = self.pipeline.as_mut().unwrap().render( 154 | future, 155 | renderer.swapchain_image_view(), 156 | gui, 157 | ); 158 | // Present swapchain 159 | renderer.present(after_future, true); 160 | } 161 | Err(vulkano::VulkanError::OutOfDate) => { 162 | renderer.resize(); 163 | } 164 | Err(e) => panic!("Failed to acquire swapchain future: {}", e), 165 | }; 166 | } 167 | _ => (), 168 | } 169 | } 170 | 171 | fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { 172 | let renderer = self.windows.get_primary_renderer().unwrap(); 173 | renderer.window().request_redraw(); 174 | } 175 | } 176 | 177 | pub fn main() -> Result<(), EventLoopError> { 178 | // Winit event loop 179 | let event_loop = EventLoop::new().unwrap(); 180 | 181 | let mut app = App::default(); 182 | 183 | event_loop.run_app(&mut app) 184 | } 185 | 186 | struct MSAAPipeline { 187 | allocator: Arc, 188 | queue: Arc, 189 | render_pass: Arc, 190 | pipeline: Arc, 191 | subpass: Subpass, 192 | intermediary: Arc, 193 | vertex_buffer: Subbuffer<[MyVertex]>, 194 | command_buffer_allocator: Arc, 195 | } 196 | 197 | impl MSAAPipeline { 198 | pub fn new( 199 | queue: Arc, 200 | image_format: vulkano::format::Format, 201 | allocator: &Arc, 202 | sample_count: SampleCount, 203 | ) -> Self { 204 | let render_pass = 205 | Self::create_render_pass(queue.device().clone(), image_format, sample_count); 206 | let (pipeline, subpass) = 207 | Self::create_pipeline(queue.device().clone(), render_pass.clone()); 208 | 209 | let vertex_buffer = Buffer::from_iter( 210 | allocator.clone(), 211 | BufferCreateInfo { usage: BufferUsage::VERTEX_BUFFER, ..Default::default() }, 212 | AllocationCreateInfo { 213 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 214 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 215 | ..Default::default() 216 | }, 217 | [ 218 | MyVertex { position: [-0.5, -0.25], color: [1.0, 0.0, 0.0, 1.0] }, 219 | MyVertex { position: [0.0, 0.5], color: [0.0, 1.0, 0.0, 1.0] }, 220 | MyVertex { position: [0.25, -0.1], color: [0.0, 0.0, 1.0, 1.0] }, 221 | ], 222 | ) 223 | .unwrap(); 224 | 225 | // Create an allocator for command-buffer data 226 | let command_buffer_allocator = StandardCommandBufferAllocator::new( 227 | queue.device().clone(), 228 | StandardCommandBufferAllocatorCreateInfo { 229 | secondary_buffer_count: 32, 230 | ..Default::default() 231 | }, 232 | ) 233 | .into(); 234 | 235 | let intermediary = ImageView::new_default( 236 | Image::new( 237 | allocator.clone(), 238 | ImageCreateInfo { 239 | image_type: ImageType::Dim2d, 240 | format: image_format, 241 | extent: [1, 1, 1], 242 | usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT, 243 | samples: sample_count, 244 | ..Default::default() 245 | }, 246 | AllocationCreateInfo::default(), 247 | ) 248 | .unwrap(), 249 | ) 250 | .unwrap(); 251 | 252 | Self { 253 | allocator: allocator.clone(), 254 | queue, 255 | render_pass, 256 | pipeline, 257 | subpass, 258 | intermediary, 259 | vertex_buffer, 260 | command_buffer_allocator, 261 | } 262 | } 263 | 264 | fn create_render_pass( 265 | device: Arc, 266 | format: Format, 267 | samples: SampleCount, 268 | ) -> Arc { 269 | vulkano::single_pass_renderpass!( 270 | device, 271 | attachments: { 272 | // The first framebuffer attachment is the intermediary image. 273 | intermediary: { 274 | format: format, 275 | samples: samples, 276 | load_op: Clear, 277 | store_op: DontCare, 278 | }, 279 | // The second framebuffer attachment is the final image. 280 | color: { 281 | format: format, 282 | samples: 1, 283 | load_op: DontCare, 284 | store_op: Store, 285 | } 286 | }, 287 | pass: { 288 | color: [intermediary], 289 | color_resolve: [color], 290 | depth_stencil: {}, 291 | } 292 | ) 293 | .unwrap() 294 | } 295 | 296 | fn gui_pass(&self) -> Subpass { 297 | self.subpass.clone() 298 | } 299 | 300 | fn create_pipeline( 301 | device: Arc, 302 | render_pass: Arc, 303 | ) -> (Arc, Subpass) { 304 | let vs = vs::load(device.clone()) 305 | .expect("failed to create shader module") 306 | .entry_point("main") 307 | .unwrap(); 308 | let fs = fs::load(device.clone()) 309 | .expect("failed to create shader module") 310 | .entry_point("main") 311 | .unwrap(); 312 | 313 | let vertex_input_state = MyVertex::per_vertex().definition(&vs).unwrap(); 314 | 315 | let stages = 316 | [PipelineShaderStageCreateInfo::new(vs), PipelineShaderStageCreateInfo::new(fs)]; 317 | 318 | let layout = PipelineLayout::new( 319 | device.clone(), 320 | PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) 321 | .into_pipeline_layout_create_info(device.clone()) 322 | .unwrap(), 323 | ) 324 | .unwrap(); 325 | 326 | let subpass = Subpass::from(render_pass, 0).unwrap(); 327 | ( 328 | GraphicsPipeline::new(device.clone(), None, GraphicsPipelineCreateInfo { 329 | stages: stages.into_iter().collect(), 330 | vertex_input_state: Some(vertex_input_state), 331 | input_assembly_state: Some(InputAssemblyState::default()), 332 | viewport_state: Some(ViewportState::default()), 333 | rasterization_state: Some(RasterizationState::default()), 334 | multisample_state: Some(MultisampleState { 335 | rasterization_samples: subpass.num_samples().unwrap(), 336 | ..MultisampleState::default() 337 | }), 338 | color_blend_state: Some(ColorBlendState::with_attachment_states( 339 | subpass.num_color_attachments(), 340 | ColorBlendAttachmentState::default(), 341 | )), 342 | dynamic_state: [DynamicState::Viewport].into_iter().collect(), 343 | subpass: Some(subpass.clone().into()), 344 | ..GraphicsPipelineCreateInfo::layout(layout) 345 | }) 346 | .unwrap(), 347 | subpass, 348 | ) 349 | } 350 | 351 | pub fn render( 352 | &mut self, 353 | before_future: Box, 354 | image: Arc, 355 | gui: &mut Gui, 356 | ) -> Box { 357 | let mut builder = AutoCommandBufferBuilder::primary( 358 | self.command_buffer_allocator.clone(), 359 | self.queue.queue_family_index(), 360 | CommandBufferUsage::OneTimeSubmit, 361 | ) 362 | .unwrap(); 363 | 364 | let dimensions = image.image().extent(); 365 | // Resize intermediary image 366 | if dimensions != self.intermediary.image().extent() { 367 | self.intermediary = ImageView::new_default( 368 | Image::new( 369 | self.allocator.clone(), 370 | ImageCreateInfo { 371 | image_type: ImageType::Dim2d, 372 | format: image.image().format(), 373 | extent: image.image().extent(), 374 | // transient_multisampled 375 | usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::SAMPLED, 376 | samples: self.subpass.num_samples().unwrap(), 377 | ..Default::default() 378 | }, 379 | AllocationCreateInfo::default(), 380 | ) 381 | .unwrap(), 382 | ) 383 | .unwrap(); 384 | } 385 | 386 | let framebuffer = Framebuffer::new(self.render_pass.clone(), FramebufferCreateInfo { 387 | attachments: vec![self.intermediary.clone(), image], 388 | ..Default::default() 389 | }) 390 | .unwrap(); 391 | 392 | // Begin render pipeline commands 393 | builder 394 | .begin_render_pass( 395 | RenderPassBeginInfo { 396 | clear_values: vec![Some([0.0, 0.0, 0.0, 1.0].into()), None], 397 | ..RenderPassBeginInfo::framebuffer(framebuffer) 398 | }, 399 | SubpassBeginInfo { 400 | contents: SubpassContents::SecondaryCommandBuffers, 401 | ..Default::default() 402 | }, 403 | ) 404 | .unwrap(); 405 | 406 | // Render first draw pass 407 | let mut secondary_builder = AutoCommandBufferBuilder::secondary( 408 | self.command_buffer_allocator.clone(), 409 | self.queue.queue_family_index(), 410 | CommandBufferUsage::MultipleSubmit, 411 | CommandBufferInheritanceInfo { 412 | render_pass: Some(self.subpass.clone().into()), 413 | ..Default::default() 414 | }, 415 | ) 416 | .unwrap(); 417 | secondary_builder 418 | .bind_pipeline_graphics(self.pipeline.clone()) 419 | .unwrap() 420 | .set_viewport( 421 | 0, 422 | [Viewport { 423 | offset: [0.0, 0.0], 424 | extent: [dimensions[0] as f32, dimensions[1] as f32], 425 | depth_range: 0.0..=1.0, 426 | }] 427 | .into_iter() 428 | .collect(), 429 | ) 430 | .unwrap() 431 | .bind_vertex_buffers(0, self.vertex_buffer.clone()) 432 | .unwrap(); 433 | unsafe { 434 | secondary_builder.draw(self.vertex_buffer.len() as u32, 1, 0, 0).unwrap(); 435 | } 436 | let cb = secondary_builder.build().unwrap(); 437 | builder.execute_commands(cb).unwrap(); 438 | 439 | // Draw gui on subpass 440 | let cb = gui.draw_on_subpass_image([dimensions[0], dimensions[1]]); 441 | builder.execute_commands(cb).unwrap(); 442 | 443 | // Last end render pass 444 | builder.end_render_pass(Default::default()).unwrap(); 445 | let command_buffer = builder.build().unwrap(); 446 | let after_future = before_future.then_execute(self.queue.clone(), command_buffer).unwrap(); 447 | 448 | after_future.boxed() 449 | } 450 | } 451 | 452 | #[repr(C)] 453 | #[derive(BufferContents, Vertex)] 454 | struct MyVertex { 455 | #[format(R32G32_SFLOAT)] 456 | position: [f32; 2], 457 | #[format(R32G32B32A32_SFLOAT)] 458 | color: [f32; 4], 459 | } 460 | 461 | mod vs { 462 | vulkano_shaders::shader! { 463 | ty: "vertex", 464 | src: " 465 | #version 450 466 | layout(location = 0) in vec2 position; 467 | layout(location = 1) in vec4 color; 468 | 469 | layout(location = 0) out vec4 v_color; 470 | void main() { 471 | gl_Position = vec4(position, 0.0, 1.0); 472 | v_color = color; 473 | }" 474 | } 475 | } 476 | 477 | mod fs { 478 | vulkano_shaders::shader! { 479 | ty: "fragment", 480 | src: " 481 | #version 450 482 | layout(location = 0) in vec4 v_color; 483 | 484 | layout(location = 0) out vec4 f_color; 485 | 486 | void main() { 487 | f_color = v_color; 488 | }" 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /examples/paint_callback.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | use std::sync::Arc; 11 | 12 | use egui::{mutex::Mutex, vec2, PaintCallback, PaintCallbackInfo, Rgba, Sense}; 13 | use egui_winit_vulkano::{CallbackContext, CallbackFn, Gui, GuiConfig, RenderResources}; 14 | use vulkano::{ 15 | buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, 16 | memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, 17 | pipeline::{ 18 | graphics::{ 19 | color_blend::{ColorBlendAttachmentState, ColorBlendState}, 20 | input_assembly::InputAssemblyState, 21 | multisample::MultisampleState, 22 | rasterization::RasterizationState, 23 | vertex_input::{Vertex, VertexDefinition}, 24 | viewport::ViewportState, 25 | GraphicsPipelineCreateInfo, 26 | }, 27 | layout::PipelineDescriptorSetLayoutCreateInfo, 28 | DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, 29 | }, 30 | }; 31 | use vulkano_util::{ 32 | context::{VulkanoConfig, VulkanoContext}, 33 | window::{VulkanoWindows, WindowDescriptor}, 34 | }; 35 | use winit::{application::ApplicationHandler, event::WindowEvent, event_loop::EventLoop}; 36 | 37 | pub struct App { 38 | context: VulkanoContext, 39 | windows: VulkanoWindows, 40 | gui: Option, 41 | scene: Option>>, 42 | } 43 | 44 | impl Default for App { 45 | fn default() -> Self { 46 | // Vulkano context 47 | let context = VulkanoContext::new(VulkanoConfig::default()); 48 | 49 | // Vulkano windows 50 | let windows = VulkanoWindows::default(); 51 | 52 | Self { context, windows, gui: None, scene: None } 53 | } 54 | } 55 | 56 | impl ApplicationHandler for App { 57 | fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { 58 | self.windows.create_window( 59 | event_loop, 60 | &self.context, 61 | &WindowDescriptor { width: 400.0, height: 400.0, ..Default::default() }, 62 | |ci| { 63 | ci.image_format = vulkano::format::Format::B8G8R8A8_UNORM; 64 | ci.min_image_count = ci.min_image_count.max(2); 65 | }, 66 | ); 67 | 68 | // Create gui as main render pass (no overlay means it clears the image each frame) 69 | let renderer = self.windows.get_primary_renderer_mut().unwrap(); 70 | 71 | let gui = Gui::new( 72 | event_loop, 73 | renderer.surface(), 74 | renderer.graphics_queue(), 75 | renderer.swapchain_format(), 76 | GuiConfig::default(), 77 | ); 78 | 79 | let scene = Arc::new(Mutex::new(Scene::new(gui.render_resources()))); 80 | 81 | self.gui = Some(gui); 82 | self.scene = Some(scene); 83 | } 84 | 85 | fn window_event( 86 | &mut self, 87 | event_loop: &winit::event_loop::ActiveEventLoop, 88 | window_id: winit::window::WindowId, 89 | event: WindowEvent, 90 | ) { 91 | let renderer = self.windows.get_renderer_mut(window_id).unwrap(); 92 | 93 | let gui = self.gui.as_mut().unwrap(); 94 | 95 | // Update Egui integration so the UI works! 96 | let _pass_events_to_game = !gui.update(&event); 97 | match event { 98 | WindowEvent::Resized(_) => { 99 | renderer.resize(); 100 | } 101 | WindowEvent::ScaleFactorChanged { .. } => { 102 | renderer.resize(); 103 | } 104 | WindowEvent::CloseRequested => { 105 | event_loop.exit(); 106 | } 107 | WindowEvent::RedrawRequested => { 108 | let scene = self.scene.clone().unwrap(); 109 | // Set immediate UI in redraw here 110 | gui.immediate_ui(|gui| { 111 | let ctx = gui.context(); 112 | egui::CentralPanel::default().show(&ctx, |ui| { 113 | // Create a frame to render our triangle image in 114 | egui::Frame::canvas(ui.style()).fill(Rgba::BLACK.into()).show(ui, |ui| { 115 | // Allocate all the space in the frame for the image 116 | let (rect, _) = ui.allocate_exact_size( 117 | vec2(ui.available_width(), ui.available_height()), 118 | Sense::click(), 119 | ); 120 | 121 | // Render the scene in the allocated space 122 | let paint_callback = PaintCallback { 123 | rect, 124 | callback: Arc::new(CallbackFn::new(move |info, context| { 125 | let mut scene = scene.lock(); 126 | scene.render(info, context); 127 | })), 128 | }; 129 | 130 | ui.painter().add(paint_callback); 131 | }); 132 | }); 133 | }); 134 | // Render UI 135 | // Acquire swapchain future 136 | match renderer.acquire(Some(std::time::Duration::from_millis(10)), |_| {}) { 137 | Ok(future) => { 138 | // Render gui 139 | let after_future = 140 | gui.draw_on_image(future, renderer.swapchain_image_view()); 141 | // Present swapchain 142 | renderer.present(after_future, true); 143 | } 144 | Err(vulkano::VulkanError::OutOfDate) => { 145 | renderer.resize(); 146 | } 147 | Err(e) => panic!("Failed to acquire swapchain future: {}", e), 148 | }; 149 | } 150 | _ => (), 151 | } 152 | } 153 | 154 | fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { 155 | let renderer = self.windows.get_primary_renderer().unwrap(); 156 | 157 | renderer.window().request_redraw(); 158 | } 159 | } 160 | 161 | pub fn main() -> Result<(), winit::error::EventLoopError> { 162 | // Winit event loop 163 | let event_loop = EventLoop::new().unwrap(); 164 | 165 | let mut app = App::default(); 166 | 167 | event_loop.run_app(&mut app) 168 | } 169 | 170 | struct Scene { 171 | pipeline: Arc, 172 | vertex_buffer: Subbuffer<[MyVertex]>, 173 | } 174 | impl Scene { 175 | pub fn new(resources: RenderResources) -> Self { 176 | // Create the vertex buffer for the triangle 177 | let vertex_buffer = Buffer::from_iter( 178 | resources.memory_allocator.clone(), 179 | BufferCreateInfo { usage: BufferUsage::VERTEX_BUFFER, ..Default::default() }, 180 | AllocationCreateInfo { 181 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 182 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 183 | ..Default::default() 184 | }, 185 | [ 186 | MyVertex { position: [-0.5, -0.25], color: [1.0, 0.0, 0.0, 1.0] }, 187 | MyVertex { position: [0.0, 0.5], color: [0.0, 1.0, 0.0, 1.0] }, 188 | MyVertex { position: [0.25, -0.1], color: [0.0, 0.0, 1.0, 1.0] }, 189 | ], 190 | ) 191 | .unwrap(); 192 | 193 | // Create the graphics pipeline 194 | let vs = vs::load(resources.queue.device().clone()) 195 | .expect("failed to create shader module") 196 | .entry_point("main") 197 | .unwrap(); 198 | let fs = fs::load(resources.queue.device().clone()) 199 | .expect("failed to create shader module") 200 | .entry_point("main") 201 | .unwrap(); 202 | 203 | let vertex_input_state = MyVertex::per_vertex().definition(&vs).unwrap(); 204 | 205 | let stages = 206 | [PipelineShaderStageCreateInfo::new(vs), PipelineShaderStageCreateInfo::new(fs)]; 207 | 208 | let layout = PipelineLayout::new( 209 | resources.queue.device().clone(), 210 | PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) 211 | .into_pipeline_layout_create_info(resources.queue.device().clone()) 212 | .unwrap(), 213 | ) 214 | .unwrap(); 215 | 216 | let pipeline = GraphicsPipeline::new( 217 | resources.queue.device().clone(), 218 | None, 219 | GraphicsPipelineCreateInfo { 220 | stages: stages.into_iter().collect(), 221 | vertex_input_state: Some(vertex_input_state), 222 | input_assembly_state: Some(InputAssemblyState::default()), 223 | viewport_state: Some(ViewportState::default()), 224 | rasterization_state: Some(RasterizationState::default()), 225 | multisample_state: Some(MultisampleState::default()), 226 | color_blend_state: Some(ColorBlendState::with_attachment_states( 227 | resources.subpass.num_color_attachments(), 228 | ColorBlendAttachmentState::default(), 229 | )), 230 | dynamic_state: [DynamicState::Viewport].into_iter().collect(), 231 | subpass: Some(resources.subpass.clone().into()), 232 | ..GraphicsPipelineCreateInfo::layout(layout) 233 | }, 234 | ) 235 | .unwrap(); 236 | 237 | // Create scene object 238 | Self { pipeline, vertex_buffer } 239 | } 240 | 241 | pub fn render(&mut self, _info: PaintCallbackInfo, context: &mut CallbackContext) { 242 | // Add the scene's rendering commands to the command buffer 243 | context 244 | .builder 245 | .bind_pipeline_graphics(self.pipeline.clone()) 246 | .unwrap() 247 | .bind_vertex_buffers(0, self.vertex_buffer.clone()) 248 | .unwrap(); 249 | unsafe { 250 | context.builder.draw(self.vertex_buffer.len() as u32, 1, 0, 0).unwrap(); 251 | } 252 | } 253 | } 254 | 255 | #[repr(C)] 256 | #[derive(BufferContents, Vertex)] 257 | struct MyVertex { 258 | #[format(R32G32_SFLOAT)] 259 | position: [f32; 2], 260 | #[format(R32G32B32A32_SFLOAT)] 261 | color: [f32; 4], 262 | } 263 | 264 | mod vs { 265 | vulkano_shaders::shader! { 266 | ty: "vertex", 267 | src: " 268 | #version 450 269 | layout(location = 0) in vec2 position; 270 | layout(location = 1) in vec4 color; 271 | 272 | layout(location = 0) out vec4 v_color; 273 | void main() { 274 | gl_Position = vec4(position, 0.0, 1.0); 275 | v_color = color; 276 | }" 277 | } 278 | } 279 | 280 | mod fs { 281 | vulkano_shaders::shader! { 282 | ty: "fragment", 283 | src: " 284 | #version 450 285 | layout(location = 0) in vec4 v_color; 286 | 287 | layout(location = 0) out vec4 f_color; 288 | 289 | void main() { 290 | f_color = v_color; 291 | }" 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /examples/subpass.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | #![allow(clippy::eq_op)] 11 | 12 | use std::sync::Arc; 13 | 14 | use egui::{epaint::Shadow, vec2, Align, Align2, Color32, CornerRadius, Frame, Margin, Window}; 15 | use egui_winit_vulkano::{Gui, GuiConfig}; 16 | use vulkano::{ 17 | buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, 18 | command_buffer::{ 19 | allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo}, 20 | AutoCommandBufferBuilder, CommandBufferInheritanceInfo, CommandBufferUsage, 21 | RenderPassBeginInfo, SubpassBeginInfo, SubpassContents, 22 | }, 23 | device::{Device, Queue}, 24 | format::Format, 25 | image::{view::ImageView, SampleCount}, 26 | memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, 27 | pipeline::{ 28 | graphics::{ 29 | color_blend::{ColorBlendAttachmentState, ColorBlendState}, 30 | input_assembly::InputAssemblyState, 31 | multisample::MultisampleState, 32 | rasterization::RasterizationState, 33 | vertex_input::{Vertex, VertexDefinition}, 34 | viewport::{Viewport, ViewportState}, 35 | GraphicsPipelineCreateInfo, 36 | }, 37 | layout::PipelineDescriptorSetLayoutCreateInfo, 38 | DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, 39 | }, 40 | render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, 41 | sync::GpuFuture, 42 | }; 43 | use vulkano_util::{ 44 | context::{VulkanoConfig, VulkanoContext}, 45 | window::{VulkanoWindows, WindowDescriptor}, 46 | }; 47 | use winit::{ 48 | application::ApplicationHandler, error::EventLoopError, event::WindowEvent, 49 | event_loop::EventLoop, 50 | }; 51 | // Render a triangle (scene) and a gui from a subpass on top of it (with some transparent fill) 52 | 53 | pub struct App { 54 | context: VulkanoContext, 55 | windows: VulkanoWindows, 56 | gui_pipeline: Option, 57 | gui: Option, 58 | } 59 | 60 | impl Default for App { 61 | fn default() -> Self { 62 | // Vulkano context 63 | let context = VulkanoContext::new(VulkanoConfig::default()); 64 | 65 | // Vulkano windows 66 | let windows = VulkanoWindows::default(); 67 | 68 | Self { context, windows, gui_pipeline: None, gui: None } 69 | } 70 | } 71 | 72 | impl ApplicationHandler for App { 73 | fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { 74 | self.windows.create_window(event_loop, &self.context, &WindowDescriptor::default(), |ci| { 75 | ci.image_format = vulkano::format::Format::B8G8R8A8_UNORM; 76 | ci.min_image_count = ci.min_image_count.max(2); 77 | }); 78 | 79 | // Create out gui pipeline 80 | let gui_pipeline = SimpleGuiPipeline::new( 81 | self.context.graphics_queue().clone(), 82 | self.windows.get_primary_renderer_mut().unwrap().swapchain_format(), 83 | self.context.memory_allocator(), 84 | ); 85 | 86 | // Create gui subpass 87 | self.gui = Some(Gui::new_with_subpass( 88 | event_loop, 89 | self.windows.get_primary_renderer_mut().unwrap().surface(), 90 | self.windows.get_primary_renderer_mut().unwrap().graphics_queue(), 91 | gui_pipeline.gui_pass(), 92 | self.windows.get_primary_renderer_mut().unwrap().swapchain_format(), 93 | GuiConfig::default(), 94 | )); 95 | 96 | self.gui_pipeline = Some(gui_pipeline); 97 | } 98 | 99 | fn window_event( 100 | &mut self, 101 | event_loop: &winit::event_loop::ActiveEventLoop, 102 | window_id: winit::window::WindowId, 103 | event: WindowEvent, 104 | ) { 105 | let renderer = self.windows.get_renderer_mut(window_id).unwrap(); 106 | 107 | let gui = self.gui.as_mut().unwrap(); 108 | 109 | match event { 110 | WindowEvent::Resized(_) => { 111 | renderer.resize(); 112 | } 113 | WindowEvent::ScaleFactorChanged { .. } => { 114 | renderer.resize(); 115 | } 116 | WindowEvent::CloseRequested => { 117 | event_loop.exit(); 118 | } 119 | WindowEvent::RedrawRequested => { 120 | // Set immediate UI in redraw here 121 | gui.immediate_ui(|gui| { 122 | let ctx = gui.context(); 123 | Window::new("Transparent Window") 124 | .anchor(Align2([Align::RIGHT, Align::TOP]), vec2(-545.0, 500.0)) 125 | .resizable(false) 126 | .default_width(300.0) 127 | .frame( 128 | Frame::NONE 129 | .fill(Color32::from_white_alpha(125)) 130 | .shadow(Shadow { 131 | spread: 8, 132 | blur: 10, 133 | color: Color32::from_black_alpha(125), 134 | ..Default::default() 135 | }) 136 | .corner_radius(CornerRadius::same(5)) 137 | .inner_margin(Margin::same(10)), 138 | ) 139 | .show(&ctx, |ui| { 140 | ui.colored_label(Color32::BLACK, "Content :)"); 141 | }); 142 | }); 143 | 144 | // Acquire swapchain future 145 | match renderer.acquire(Some(std::time::Duration::from_millis(10)), |_| {}) { 146 | Ok(future) => { 147 | // Render gui 148 | let after_future = self.gui_pipeline.as_mut().unwrap().render( 149 | future, 150 | renderer.swapchain_image_view(), 151 | gui, 152 | ); 153 | 154 | // Present swapchain 155 | renderer.present(after_future, true); 156 | } 157 | Err(vulkano::VulkanError::OutOfDate) => { 158 | renderer.resize(); 159 | } 160 | Err(e) => panic!("Failed to acquire swapchain future: {}", e), 161 | }; 162 | } 163 | _ => (), 164 | } 165 | 166 | if window_id == renderer.window().id() { 167 | // Update Egui integration so the UI works! 168 | let _pass_events_to_game = !gui.update(&event); 169 | } 170 | } 171 | 172 | fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { 173 | let renderer = self.windows.get_primary_renderer().unwrap(); 174 | 175 | renderer.window().request_redraw(); 176 | } 177 | } 178 | 179 | pub fn main() -> Result<(), EventLoopError> { 180 | // Winit event loop 181 | let event_loop = EventLoop::new().unwrap(); 182 | 183 | let mut app = App::default(); 184 | 185 | event_loop.run_app(&mut app) 186 | } 187 | 188 | struct SimpleGuiPipeline { 189 | queue: Arc, 190 | render_pass: Arc, 191 | pipeline: Arc, 192 | subpass: Subpass, 193 | vertex_buffer: Subbuffer<[MyVertex]>, 194 | command_buffer_allocator: Arc, 195 | } 196 | 197 | impl SimpleGuiPipeline { 198 | pub fn new( 199 | queue: Arc, 200 | image_format: vulkano::format::Format, 201 | allocator: &Arc, 202 | ) -> Self { 203 | let render_pass = Self::create_render_pass(queue.device().clone(), image_format); 204 | let (pipeline, subpass) = 205 | Self::create_pipeline(queue.device().clone(), render_pass.clone()); 206 | 207 | let vertex_buffer = Buffer::from_iter( 208 | allocator.clone(), 209 | BufferCreateInfo { usage: BufferUsage::VERTEX_BUFFER, ..Default::default() }, 210 | AllocationCreateInfo { 211 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 212 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 213 | ..Default::default() 214 | }, 215 | [ 216 | MyVertex { position: [-0.5, -0.25], color: [1.0, 0.0, 0.0, 1.0] }, 217 | MyVertex { position: [0.0, 0.5], color: [0.0, 1.0, 0.0, 1.0] }, 218 | MyVertex { position: [0.25, -0.1], color: [0.0, 0.0, 1.0, 1.0] }, 219 | ], 220 | ) 221 | .unwrap(); 222 | 223 | // Create an allocator for command-buffer data 224 | let command_buffer_allocator = StandardCommandBufferAllocator::new( 225 | queue.device().clone(), 226 | StandardCommandBufferAllocatorCreateInfo { 227 | secondary_buffer_count: 32, 228 | ..Default::default() 229 | }, 230 | ) 231 | .into(); 232 | 233 | Self { queue, render_pass, pipeline, subpass, vertex_buffer, command_buffer_allocator } 234 | } 235 | 236 | fn create_render_pass(device: Arc, format: Format) -> Arc { 237 | vulkano::ordered_passes_renderpass!( 238 | device, 239 | attachments: { 240 | color: { 241 | format: format, 242 | samples: SampleCount::Sample1, 243 | load_op: Clear, 244 | store_op: Store, 245 | } 246 | }, 247 | passes: [ 248 | { color: [color], depth_stencil: {}, input: [] }, // Draw what you want on this pass 249 | { color: [color], depth_stencil: {}, input: [] } // Gui render pass 250 | ] 251 | ) 252 | .unwrap() 253 | } 254 | 255 | fn gui_pass(&self) -> Subpass { 256 | Subpass::from(self.render_pass.clone(), 1).unwrap() 257 | } 258 | 259 | fn create_pipeline( 260 | device: Arc, 261 | render_pass: Arc, 262 | ) -> (Arc, Subpass) { 263 | let vs = vs::load(device.clone()) 264 | .expect("failed to create shader module") 265 | .entry_point("main") 266 | .unwrap(); 267 | let fs = fs::load(device.clone()) 268 | .expect("failed to create shader module") 269 | .entry_point("main") 270 | .unwrap(); 271 | 272 | let vertex_input_state = MyVertex::per_vertex().definition(&vs).unwrap(); 273 | 274 | let stages = 275 | [PipelineShaderStageCreateInfo::new(vs), PipelineShaderStageCreateInfo::new(fs)]; 276 | 277 | let layout = PipelineLayout::new( 278 | device.clone(), 279 | PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) 280 | .into_pipeline_layout_create_info(device.clone()) 281 | .unwrap(), 282 | ) 283 | .unwrap(); 284 | 285 | let subpass = Subpass::from(render_pass, 0).unwrap(); 286 | ( 287 | GraphicsPipeline::new(device, None, GraphicsPipelineCreateInfo { 288 | stages: stages.into_iter().collect(), 289 | vertex_input_state: Some(vertex_input_state), 290 | input_assembly_state: Some(InputAssemblyState::default()), 291 | viewport_state: Some(ViewportState::default()), 292 | rasterization_state: Some(RasterizationState::default()), 293 | multisample_state: Some(MultisampleState::default()), 294 | color_blend_state: Some(ColorBlendState::with_attachment_states( 295 | subpass.num_color_attachments(), 296 | ColorBlendAttachmentState::default(), 297 | )), 298 | dynamic_state: [DynamicState::Viewport].into_iter().collect(), 299 | subpass: Some(subpass.clone().into()), 300 | ..GraphicsPipelineCreateInfo::layout(layout) 301 | }) 302 | .unwrap(), 303 | subpass, 304 | ) 305 | } 306 | 307 | pub fn render( 308 | &mut self, 309 | before_future: Box, 310 | image: Arc, 311 | gui: &mut Gui, 312 | ) -> Box { 313 | let mut builder = AutoCommandBufferBuilder::primary( 314 | self.command_buffer_allocator.clone(), 315 | self.queue.queue_family_index(), 316 | CommandBufferUsage::OneTimeSubmit, 317 | ) 318 | .unwrap(); 319 | 320 | let dimensions = image.image().extent(); 321 | let framebuffer = Framebuffer::new(self.render_pass.clone(), FramebufferCreateInfo { 322 | attachments: vec![image], 323 | ..Default::default() 324 | }) 325 | .unwrap(); 326 | 327 | // Begin render pipeline commands 328 | builder 329 | .begin_render_pass( 330 | RenderPassBeginInfo { 331 | clear_values: vec![Some([0.0, 0.0, 0.0, 1.0].into())], 332 | ..RenderPassBeginInfo::framebuffer(framebuffer) 333 | }, 334 | SubpassBeginInfo { 335 | contents: SubpassContents::SecondaryCommandBuffers, 336 | ..Default::default() 337 | }, 338 | ) 339 | .unwrap(); 340 | 341 | // Render first draw pass 342 | let mut secondary_builder = AutoCommandBufferBuilder::secondary( 343 | self.command_buffer_allocator.clone(), 344 | self.queue.queue_family_index(), 345 | CommandBufferUsage::MultipleSubmit, 346 | CommandBufferInheritanceInfo { 347 | render_pass: Some(self.subpass.clone().into()), 348 | ..Default::default() 349 | }, 350 | ) 351 | .unwrap(); 352 | secondary_builder 353 | .bind_pipeline_graphics(self.pipeline.clone()) 354 | .unwrap() 355 | .set_viewport( 356 | 0, 357 | [Viewport { 358 | offset: [0.0, 0.0], 359 | extent: [dimensions[0] as f32, dimensions[1] as f32], 360 | depth_range: 0.0..=1.0, 361 | }] 362 | .into_iter() 363 | .collect(), 364 | ) 365 | .unwrap() 366 | .bind_vertex_buffers(0, self.vertex_buffer.clone()) 367 | .unwrap(); 368 | unsafe { 369 | secondary_builder.draw(self.vertex_buffer.len() as u32, 1, 0, 0).unwrap(); 370 | } 371 | let cb = secondary_builder.build().unwrap(); 372 | builder.execute_commands(cb).unwrap(); 373 | 374 | // Move on to next subpass for gui 375 | builder 376 | .next_subpass(Default::default(), SubpassBeginInfo { 377 | contents: SubpassContents::SecondaryCommandBuffers, 378 | ..Default::default() 379 | }) 380 | .unwrap(); 381 | // Draw gui on subpass 382 | let cb = gui.draw_on_subpass_image([dimensions[0], dimensions[1]]); 383 | builder.execute_commands(cb).unwrap(); 384 | 385 | // Last end render pass 386 | builder.end_render_pass(Default::default()).unwrap(); 387 | let command_buffer = builder.build().unwrap(); 388 | let after_future = before_future.then_execute(self.queue.clone(), command_buffer).unwrap(); 389 | 390 | after_future.boxed() 391 | } 392 | } 393 | 394 | #[repr(C)] 395 | #[derive(BufferContents, Vertex)] 396 | struct MyVertex { 397 | #[format(R32G32_SFLOAT)] 398 | position: [f32; 2], 399 | #[format(R32G32B32A32_SFLOAT)] 400 | color: [f32; 4], 401 | } 402 | 403 | mod vs { 404 | vulkano_shaders::shader! { 405 | ty: "vertex", 406 | src: " 407 | #version 450 408 | layout(location = 0) in vec2 position; 409 | layout(location = 1) in vec4 color; 410 | 411 | layout(location = 0) out vec4 v_color; 412 | void main() { 413 | gl_Position = vec4(position, 0.0, 1.0); 414 | v_color = color; 415 | }" 416 | } 417 | } 418 | 419 | mod fs { 420 | vulkano_shaders::shader! { 421 | ty: "fragment", 422 | src: " 423 | #version 450 424 | layout(location = 0) in vec4 v_color; 425 | 426 | layout(location = 0) out vec4 f_color; 427 | 428 | void main() { 429 | f_color = v_color; 430 | }" 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /examples/wholesome/assets/doge2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakolao/egui_winit_vulkano/0ae6e4e8372906ef6db90b39e9b0f9bbfccc904b/examples/wholesome/assets/doge2.png -------------------------------------------------------------------------------- /examples/wholesome/assets/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakolao/egui_winit_vulkano/0ae6e4e8372906ef6db90b39e9b0f9bbfccc904b/examples/wholesome/assets/tree.png -------------------------------------------------------------------------------- /examples/wholesome/frame_system.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 The vulkano developers <=== ! 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | // This is a simplified version of the example. See that for commented version of this code. 11 | // https://github.com/vulkano-rs/vulkano-examples/blob/master/src/bin/deferred/frame/system.rs 12 | // Egui drawing could be its own pass or it could be a deferred subpass 13 | 14 | use std::sync::Arc; 15 | 16 | use cgmath::Matrix4; 17 | use vulkano::{ 18 | command_buffer::{ 19 | AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, 20 | RenderPassBeginInfo, SecondaryAutoCommandBuffer, SubpassBeginInfo, SubpassContents, 21 | }, 22 | device::Queue, 23 | format::Format, 24 | image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage}, 25 | memory::allocator::AllocationCreateInfo, 26 | render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, 27 | sync::GpuFuture, 28 | }; 29 | 30 | use crate::renderer::Allocators; 31 | 32 | /// System that contains the necessary facilities for rendering a single frame. 33 | pub struct FrameSystem { 34 | gfx_queue: Arc, 35 | render_pass: Arc, 36 | depth_buffer: Arc, 37 | allocators: Allocators, 38 | } 39 | 40 | impl FrameSystem { 41 | pub fn new( 42 | gfx_queue: Arc, 43 | final_output_format: Format, 44 | allocators: Allocators, 45 | ) -> FrameSystem { 46 | let render_pass = vulkano::ordered_passes_renderpass!(gfx_queue.device().clone(), 47 | attachments: { 48 | final_color: { 49 | format: final_output_format, 50 | samples: 1, 51 | load_op: Clear, 52 | store_op: Store, 53 | }, 54 | depth: { 55 | format: Format::D16_UNORM, 56 | samples: 1, 57 | load_op: Clear, 58 | store_op: DontCare, 59 | } 60 | }, 61 | passes: [ 62 | { 63 | color: [final_color], 64 | depth_stencil: {depth}, 65 | input: [] 66 | } 67 | ] 68 | ) 69 | .unwrap(); 70 | 71 | let depth_buffer = Image::new( 72 | allocators.memory.clone(), 73 | ImageCreateInfo { 74 | image_type: ImageType::Dim2d, 75 | format: Format::D16_UNORM, 76 | extent: [1, 1, 1], 77 | array_layers: 1, 78 | usage: ImageUsage::SAMPLED | ImageUsage::DEPTH_STENCIL_ATTACHMENT, 79 | ..Default::default() 80 | }, 81 | AllocationCreateInfo::default(), 82 | ) 83 | .unwrap(); 84 | let depth_buffer = ImageView::new_default(depth_buffer.clone()).unwrap(); 85 | FrameSystem { gfx_queue, render_pass, depth_buffer, allocators } 86 | } 87 | 88 | #[inline] 89 | pub fn deferred_subpass(&self) -> Subpass { 90 | Subpass::from(self.render_pass.clone(), 0).unwrap() 91 | } 92 | 93 | pub fn frame( 94 | &mut self, 95 | before_future: F, 96 | final_image: Arc, 97 | world_to_framebuffer: Matrix4, 98 | ) -> Frame 99 | where 100 | F: GpuFuture + 'static, 101 | { 102 | let img_dims = final_image.image().extent(); 103 | if self.depth_buffer.image().extent() != img_dims { 104 | self.depth_buffer = ImageView::new_default( 105 | Image::new( 106 | self.allocators.memory.clone(), 107 | ImageCreateInfo { 108 | image_type: ImageType::Dim2d, 109 | format: Format::D16_UNORM, 110 | extent: final_image.image().extent(), 111 | array_layers: 1, 112 | usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT 113 | | ImageUsage::TRANSIENT_ATTACHMENT, 114 | ..Default::default() 115 | }, 116 | AllocationCreateInfo::default(), 117 | ) 118 | .unwrap(), 119 | ) 120 | .unwrap(); 121 | } 122 | let framebuffer = Framebuffer::new(self.render_pass.clone(), FramebufferCreateInfo { 123 | attachments: vec![final_image, self.depth_buffer.clone()], 124 | ..Default::default() 125 | }) 126 | .unwrap(); 127 | let mut command_buffer_builder = AutoCommandBufferBuilder::primary( 128 | self.allocators.command_buffers.clone(), 129 | self.gfx_queue.queue_family_index(), 130 | CommandBufferUsage::OneTimeSubmit, 131 | ) 132 | .unwrap(); 133 | command_buffer_builder 134 | .begin_render_pass( 135 | RenderPassBeginInfo { 136 | clear_values: vec![Some([0.0, 0.0, 0.0, 0.0].into()), Some(1.0f32.into())], 137 | ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) 138 | }, 139 | SubpassBeginInfo { 140 | contents: SubpassContents::SecondaryCommandBuffers, 141 | ..Default::default() 142 | }, 143 | ) 144 | .unwrap(); 145 | 146 | Frame { 147 | system: self, 148 | before_main_cb_future: Some(Box::new(before_future)), 149 | framebuffer, 150 | num_pass: 0, 151 | recording_command_buffer: Some(command_buffer_builder), 152 | world_to_framebuffer, 153 | } 154 | } 155 | } 156 | 157 | pub struct Frame<'a> { 158 | system: &'a mut FrameSystem, 159 | num_pass: u8, 160 | before_main_cb_future: Option>, 161 | framebuffer: Arc, 162 | recording_command_buffer: Option>, 163 | #[allow(dead_code)] 164 | world_to_framebuffer: Matrix4, 165 | } 166 | 167 | impl<'a> Frame<'a> { 168 | pub fn next_pass<'f>(&'f mut self) -> Option> { 169 | let current_pass = { 170 | let current_pass = self.num_pass; 171 | self.num_pass += 1; 172 | current_pass 173 | }; 174 | match current_pass { 175 | 0 => Some(Pass::Deferred(DrawPass { frame: self })), 176 | 1 => { 177 | self.recording_command_buffer 178 | .as_mut() 179 | .unwrap() 180 | .end_render_pass(Default::default()) 181 | .unwrap(); 182 | let command_buffer = self.recording_command_buffer.take().unwrap().build().unwrap(); 183 | let after_main_cb = self 184 | .before_main_cb_future 185 | .take() 186 | .unwrap() 187 | .then_execute(self.system.gfx_queue.clone(), command_buffer) 188 | .unwrap(); 189 | Some(Pass::Finished(Box::new(after_main_cb))) 190 | } 191 | _ => None, 192 | } 193 | } 194 | } 195 | 196 | pub enum Pass<'f, 's: 'f> { 197 | Deferred(DrawPass<'f, 's>), 198 | Finished(Box), 199 | } 200 | 201 | pub struct DrawPass<'f, 's: 'f> { 202 | frame: &'f mut Frame<'s>, 203 | } 204 | 205 | impl<'f, 's: 'f> DrawPass<'f, 's> { 206 | #[inline] 207 | pub fn execute(&mut self, command_buffer: Arc) { 208 | self.frame 209 | .recording_command_buffer 210 | .as_mut() 211 | .unwrap() 212 | .execute_commands(command_buffer) 213 | .unwrap(); 214 | } 215 | 216 | #[allow(dead_code)] 217 | #[inline] 218 | pub fn viewport_dimensions(&self) -> [u32; 2] { 219 | self.frame.framebuffer.extent() 220 | } 221 | 222 | #[allow(dead_code)] 223 | #[inline] 224 | pub fn world_to_framebuffer_matrix(&self) -> Matrix4 { 225 | self.frame.world_to_framebuffer 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /examples/wholesome/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | #![allow(clippy::eq_op)] 11 | 12 | use std::sync::Arc; 13 | 14 | use egui::{load::SizedTexture, Context, ImageSource, Visuals}; 15 | use egui_winit_vulkano::{Gui, GuiConfig}; 16 | use vulkano::{ 17 | command_buffer::allocator::{ 18 | StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo, 19 | }, 20 | format::Format, 21 | image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage}, 22 | memory::allocator::AllocationCreateInfo, 23 | }; 24 | use vulkano_util::{ 25 | context::{VulkanoConfig, VulkanoContext}, 26 | renderer::DEFAULT_IMAGE_FORMAT, 27 | window::{VulkanoWindows, WindowDescriptor}, 28 | }; 29 | use winit::{application::ApplicationHandler, event::WindowEvent, event_loop::EventLoop}; 30 | 31 | use crate::{renderer::RenderPipeline, time_info::TimeInfo}; 32 | 33 | mod frame_system; 34 | mod renderer; 35 | mod time_info; 36 | mod triangle_draw_system; 37 | 38 | /// Example struct to contain the state of the UI 39 | pub struct GuiState { 40 | show_texture_window1: bool, 41 | show_texture_window2: bool, 42 | show_scene_window: bool, 43 | image_texture_id1: egui::TextureId, 44 | image_texture_id2: egui::TextureId, 45 | scene_texture_id: egui::TextureId, 46 | scene_view_size: [u32; 2], 47 | } 48 | 49 | impl GuiState { 50 | pub fn new(gui: &mut Gui, scene_image: Arc, scene_view_size: [u32; 2]) -> GuiState { 51 | // tree.png asset is from https://github.com/sotrh/learn-wgpu/tree/master/docs/beginner/tutorial5-textures 52 | let image_texture_id1 = gui.register_user_image( 53 | include_bytes!("./assets/tree.png"), 54 | Format::R8G8B8A8_SRGB, 55 | Default::default(), 56 | ); 57 | let image_texture_id2 = gui.register_user_image( 58 | include_bytes!("./assets/doge2.png"), 59 | Format::R8G8B8A8_SRGB, 60 | Default::default(), 61 | ); 62 | 63 | GuiState { 64 | show_texture_window1: true, 65 | show_texture_window2: true, 66 | show_scene_window: true, 67 | image_texture_id1, 68 | image_texture_id2, 69 | scene_texture_id: gui.register_user_image_view(scene_image, Default::default()), 70 | scene_view_size, 71 | } 72 | } 73 | 74 | /// Defines the layout of our UI 75 | pub fn layout(&mut self, egui_context: Context, window_size: [f32; 2], fps: f32) { 76 | let GuiState { 77 | show_texture_window1, 78 | show_texture_window2, 79 | show_scene_window, 80 | image_texture_id1, 81 | image_texture_id2, 82 | scene_view_size, 83 | scene_texture_id, 84 | .. 85 | } = self; 86 | egui_context.set_visuals(Visuals::dark()); 87 | egui::SidePanel::left("Side Panel").default_width(150.0).show(&egui_context, |ui| { 88 | ui.heading("Hello Tree"); 89 | ui.separator(); 90 | ui.checkbox(show_texture_window1, "Show Tree"); 91 | ui.checkbox(show_texture_window2, "Show Doge"); 92 | ui.checkbox(show_scene_window, "Show Scene"); 93 | }); 94 | 95 | egui::Window::new("Mah Tree") 96 | .resizable(true) 97 | .vscroll(true) 98 | .open(show_texture_window1) 99 | .show(&egui_context, |ui| { 100 | ui.image(ImageSource::Texture(SizedTexture::new(*image_texture_id1, [ 101 | 256.0, 256.0, 102 | ]))); 103 | }); 104 | egui::Window::new("Mah Doge") 105 | .resizable(true) 106 | .vscroll(true) 107 | .open(show_texture_window2) 108 | .show(&egui_context, |ui| { 109 | ui.image(ImageSource::Texture(SizedTexture::new(*image_texture_id2, [ 110 | 300.0, 200.0, 111 | ]))); 112 | }); 113 | egui::Window::new("Scene").resizable(true).vscroll(true).open(show_scene_window).show( 114 | &egui_context, 115 | |ui| { 116 | ui.image(ImageSource::Texture(SizedTexture::new(*scene_texture_id, [ 117 | scene_view_size[0] as f32, 118 | scene_view_size[1] as f32, 119 | ]))); 120 | }, 121 | ); 122 | egui::Area::new("fps".into()) 123 | .fixed_pos(egui::pos2(window_size[0] - 0.05 * window_size[0], 10.0)) 124 | .show(&egui_context, |ui| { 125 | ui.label(format!("{fps:.2}")); 126 | }); 127 | } 128 | } 129 | 130 | pub struct App { 131 | context: VulkanoContext, 132 | windows: VulkanoWindows, 133 | scene_view_size: [u32; 2], 134 | scene_image: Arc, 135 | time: TimeInfo, 136 | scene_render_pipeline: RenderPipeline, 137 | gui: Option, 138 | gui_state: Option, 139 | } 140 | 141 | impl Default for App { 142 | fn default() -> Self { 143 | // Vulkano context 144 | let context = VulkanoContext::new(VulkanoConfig::default()); 145 | 146 | // Vulkano windows 147 | let windows = VulkanoWindows::default(); 148 | 149 | // Create renderer for our scene & ui 150 | let scene_view_size = [256, 256]; 151 | // Create a simple image to which we'll draw the triangle scene 152 | let scene_image = ImageView::new_default( 153 | Image::new( 154 | context.memory_allocator().clone(), 155 | ImageCreateInfo { 156 | image_type: ImageType::Dim2d, 157 | format: DEFAULT_IMAGE_FORMAT, 158 | extent: [scene_view_size[0], scene_view_size[1], 1], 159 | array_layers: 1, 160 | usage: ImageUsage::SAMPLED | ImageUsage::COLOR_ATTACHMENT, 161 | ..Default::default() 162 | }, 163 | AllocationCreateInfo::default(), 164 | ) 165 | .unwrap(), 166 | ) 167 | .unwrap(); 168 | 169 | let time = TimeInfo::new(); 170 | 171 | // Create our render pipeline 172 | let scene_render_pipeline = RenderPipeline::new( 173 | context.graphics_queue().clone(), 174 | DEFAULT_IMAGE_FORMAT, 175 | &renderer::Allocators { 176 | command_buffers: Arc::new(StandardCommandBufferAllocator::new( 177 | context.device().clone(), 178 | StandardCommandBufferAllocatorCreateInfo { 179 | secondary_buffer_count: 32, 180 | ..Default::default() 181 | }, 182 | )), 183 | memory: context.memory_allocator().clone(), 184 | }, 185 | ); 186 | 187 | Self { 188 | context, 189 | windows, 190 | scene_view_size, 191 | scene_image, 192 | time, 193 | scene_render_pipeline, 194 | gui: None, 195 | gui_state: None, 196 | } 197 | } 198 | } 199 | 200 | impl ApplicationHandler for App { 201 | fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { 202 | self.windows.create_window(event_loop, &self.context, &WindowDescriptor::default(), |ci| { 203 | ci.image_format = Format::B8G8R8A8_UNORM; 204 | ci.min_image_count = ci.min_image_count.max(2); 205 | }); 206 | // Create gui as main render pass (no overlay means it clears the image each frame) 207 | let mut gui = { 208 | let renderer = self.windows.get_primary_renderer_mut().unwrap(); 209 | Gui::new( 210 | event_loop, 211 | renderer.surface(), 212 | renderer.graphics_queue(), 213 | renderer.swapchain_format(), 214 | GuiConfig::default(), 215 | ) 216 | }; 217 | 218 | // Create gui state (pass anything your state requires) 219 | self.gui_state = 220 | Some(GuiState::new(&mut gui, self.scene_image.clone(), self.scene_view_size)); 221 | 222 | self.gui = Some(gui); 223 | } 224 | 225 | fn window_event( 226 | &mut self, 227 | event_loop: &winit::event_loop::ActiveEventLoop, 228 | window_id: winit::window::WindowId, 229 | event: WindowEvent, 230 | ) { 231 | let renderer = self.windows.get_renderer_mut(window_id).unwrap(); 232 | 233 | let gui = self.gui.as_mut().unwrap(); 234 | 235 | if window_id == renderer.window().id() { 236 | let _pass_events_to_game = !gui.update(&event); 237 | match event { 238 | WindowEvent::Resized(_) => { 239 | renderer.resize(); 240 | } 241 | WindowEvent::ScaleFactorChanged { .. } => { 242 | renderer.resize(); 243 | } 244 | WindowEvent::CloseRequested => { 245 | event_loop.exit(); 246 | } 247 | WindowEvent::RedrawRequested => { 248 | // Set immediate UI in redraw here 249 | // It's a closure giving access to egui context inside which you can call anything. 250 | // Here we're calling the layout of our `gui_state`. 251 | gui.immediate_ui(|gui| { 252 | let ctx = gui.context(); 253 | self.gui_state.as_mut().unwrap().layout( 254 | ctx, 255 | renderer.window_size(), 256 | self.time.fps(), 257 | ) 258 | }); 259 | // Render UI 260 | // Acquire swapchain future 261 | match renderer.acquire(Some(std::time::Duration::from_millis(10)), |_| {}) { 262 | Ok(future) => { 263 | // Draw scene 264 | let after_scene_draw = 265 | self.scene_render_pipeline.render(future, self.scene_image.clone()); 266 | // Render gui 267 | let after_future = gui 268 | .draw_on_image(after_scene_draw, renderer.swapchain_image_view()); 269 | // Present swapchain 270 | renderer.present(after_future, true); 271 | } 272 | Err(vulkano::VulkanError::OutOfDate) => { 273 | renderer.resize(); 274 | } 275 | Err(e) => panic!("Failed to acquire swapchain future: {}", e), 276 | }; 277 | 278 | // Update fps & dt 279 | self.time.update(); 280 | } 281 | _ => (), 282 | } 283 | } 284 | } 285 | 286 | fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { 287 | let renderer = self.windows.get_primary_renderer_mut().unwrap(); 288 | renderer.window().request_redraw(); 289 | } 290 | } 291 | 292 | pub fn main() -> Result<(), winit::error::EventLoopError> { 293 | // Winit event loop & our time tracking initialization 294 | let event_loop = EventLoop::new().unwrap(); 295 | 296 | let mut app = App::default(); 297 | 298 | // Event loop run 299 | event_loop.run_app(&mut app) 300 | } 301 | -------------------------------------------------------------------------------- /examples/wholesome/renderer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | use std::sync::Arc; 11 | 12 | use cgmath::{Matrix4, SquareMatrix}; 13 | use vulkano::{ 14 | command_buffer::allocator::StandardCommandBufferAllocator, device::Queue, format::Format, 15 | image::view::ImageView, memory::allocator::StandardMemoryAllocator, sync::GpuFuture, 16 | }; 17 | 18 | use crate::{ 19 | frame_system::{FrameSystem, Pass}, 20 | triangle_draw_system::TriangleDrawSystem, 21 | }; 22 | 23 | pub struct RenderPipeline { 24 | frame_system: FrameSystem, 25 | draw_pipeline: TriangleDrawSystem, 26 | } 27 | 28 | #[derive(Clone)] 29 | pub struct Allocators { 30 | pub command_buffers: Arc, 31 | pub memory: Arc, 32 | } 33 | 34 | impl RenderPipeline { 35 | pub fn new(queue: Arc, image_format: Format, allocators: &Allocators) -> Self { 36 | let frame_system = FrameSystem::new(queue.clone(), image_format, allocators.clone()); 37 | let draw_pipeline = 38 | TriangleDrawSystem::new(queue, frame_system.deferred_subpass(), allocators); 39 | 40 | Self { frame_system, draw_pipeline } 41 | } 42 | 43 | /// Renders the pass for scene on scene images 44 | pub fn render( 45 | &mut self, 46 | before_future: Box, 47 | image: Arc, 48 | ) -> Box { 49 | let mut frame = self.frame_system.frame( 50 | before_future, 51 | // Notice that final image is now scene image 52 | image.clone(), 53 | Matrix4::identity(), 54 | ); 55 | let dims = image.image().extent(); 56 | // Draw each render pass that's related to scene 57 | let mut after_future = None; 58 | while let Some(pass) = frame.next_pass() { 59 | match pass { 60 | Pass::Deferred(mut draw_pass) => { 61 | let cb = self.draw_pipeline.draw([dims[0], dims[1]]); 62 | draw_pass.execute(cb); 63 | } 64 | Pass::Finished(af) => { 65 | after_future = Some(af); 66 | } 67 | } 68 | } 69 | after_future.unwrap().then_signal_fence_and_flush().unwrap().boxed() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/wholesome/time_info.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | use std::time::Instant; 11 | 12 | const NANOS_PER_MILLI: f32 = 1_000_000f32; 13 | 14 | pub struct TimeInfo { 15 | dt: f32, 16 | fps: f32, 17 | frame_sum: f32, 18 | dt_sum: f32, 19 | prev_time: Instant, 20 | } 21 | 22 | impl TimeInfo { 23 | pub fn new() -> TimeInfo { 24 | TimeInfo { dt: 0.0, fps: 0.0, frame_sum: 0.0, dt_sum: 0.0, prev_time: Instant::now() } 25 | } 26 | 27 | #[allow(dead_code)] 28 | pub fn dt(&self) -> f32 { 29 | self.dt 30 | } 31 | 32 | pub fn fps(&self) -> f32 { 33 | self.fps 34 | } 35 | 36 | pub fn update(&mut self) { 37 | let now = Instant::now(); 38 | self.frame_sum += 1.0; 39 | // Assume duration is never over full second, so ignore whole seconds in Duration 40 | self.dt = now.duration_since(self.prev_time).subsec_nanos() as f32 / NANOS_PER_MILLI; 41 | self.dt_sum += self.dt; 42 | if self.dt_sum >= 1000.0 { 43 | self.fps = 1000.0 / (self.dt_sum / self.frame_sum); 44 | self.dt_sum = 0.0; 45 | self.frame_sum = 0.0; 46 | } 47 | self.prev_time = now; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/wholesome/triangle_draw_system.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 The vulkano developers <=== ! 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | // Slightly modified version from 11 | // https://github.com/vulkano-rs/vulkano-examples/blob/master/src/bin/deferred/triangle_draw_system.rs 12 | // To simplify this wholesome example :) 13 | 14 | use std::sync::Arc; 15 | 16 | use vulkano::{ 17 | buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, 18 | command_buffer::{ 19 | allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, 20 | CommandBufferInheritanceInfo, CommandBufferUsage, SecondaryAutoCommandBuffer, 21 | }, 22 | device::Queue, 23 | memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, 24 | pipeline::{ 25 | graphics::{ 26 | color_blend::{ColorBlendAttachmentState, ColorBlendState}, 27 | depth_stencil::DepthStencilState, 28 | input_assembly::InputAssemblyState, 29 | multisample::MultisampleState, 30 | rasterization::RasterizationState, 31 | vertex_input::{Vertex, VertexDefinition}, 32 | viewport::{Viewport, ViewportState}, 33 | GraphicsPipelineCreateInfo, 34 | }, 35 | layout::PipelineDescriptorSetLayoutCreateInfo, 36 | DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, 37 | }, 38 | render_pass::Subpass, 39 | }; 40 | 41 | use crate::renderer::Allocators; 42 | 43 | pub struct TriangleDrawSystem { 44 | gfx_queue: Arc, 45 | vertex_buffer: Subbuffer<[MyVertex]>, 46 | pipeline: Arc, 47 | subpass: Subpass, 48 | command_buffer_allocator: Arc, 49 | } 50 | 51 | impl TriangleDrawSystem { 52 | pub fn new( 53 | gfx_queue: Arc, 54 | subpass: Subpass, 55 | allocators: &Allocators, 56 | ) -> TriangleDrawSystem { 57 | let vertex_buffer = Buffer::from_iter( 58 | allocators.memory.clone(), 59 | BufferCreateInfo { usage: BufferUsage::VERTEX_BUFFER, ..Default::default() }, 60 | AllocationCreateInfo { 61 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 62 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 63 | ..Default::default() 64 | }, 65 | [ 66 | MyVertex { position: [-0.5, -0.25], color: [1.0, 0.0, 0.0, 1.0] }, 67 | MyVertex { position: [0.0, 0.5], color: [0.0, 1.0, 0.0, 1.0] }, 68 | MyVertex { position: [0.25, -0.1], color: [0.0, 0.0, 1.0, 1.0] }, 69 | ], 70 | ) 71 | .unwrap(); 72 | 73 | let pipeline = { 74 | let vs = vs::load(gfx_queue.device().clone()) 75 | .expect("failed to create shader module") 76 | .entry_point("main") 77 | .unwrap(); 78 | let fs = fs::load(gfx_queue.device().clone()) 79 | .expect("failed to create shader module") 80 | .entry_point("main") 81 | .unwrap(); 82 | 83 | let vertex_input_state = MyVertex::per_vertex().definition(&vs).unwrap(); 84 | 85 | let stages = 86 | [PipelineShaderStageCreateInfo::new(vs), PipelineShaderStageCreateInfo::new(fs)]; 87 | 88 | let layout = PipelineLayout::new( 89 | gfx_queue.device().clone(), 90 | PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) 91 | .into_pipeline_layout_create_info(gfx_queue.device().clone()) 92 | .unwrap(), 93 | ) 94 | .unwrap(); 95 | 96 | GraphicsPipeline::new(gfx_queue.device().clone(), None, GraphicsPipelineCreateInfo { 97 | stages: stages.into_iter().collect(), 98 | vertex_input_state: Some(vertex_input_state), 99 | input_assembly_state: Some(InputAssemblyState::default()), 100 | viewport_state: Some(ViewportState::default()), 101 | rasterization_state: Some(RasterizationState::default()), 102 | multisample_state: Some(MultisampleState::default()), 103 | color_blend_state: Some(ColorBlendState::with_attachment_states( 104 | subpass.num_color_attachments(), 105 | ColorBlendAttachmentState::default(), 106 | )), 107 | depth_stencil_state: Some(DepthStencilState::default()), 108 | dynamic_state: [DynamicState::Viewport].into_iter().collect(), 109 | subpass: Some(subpass.clone().into()), 110 | ..GraphicsPipelineCreateInfo::layout(layout) 111 | }) 112 | .unwrap() 113 | }; 114 | 115 | TriangleDrawSystem { 116 | gfx_queue, 117 | vertex_buffer, 118 | pipeline, 119 | subpass, 120 | command_buffer_allocator: allocators.command_buffers.clone(), 121 | } 122 | } 123 | 124 | pub fn draw(&self, viewport_dimensions: [u32; 2]) -> Arc { 125 | let mut builder = AutoCommandBufferBuilder::secondary( 126 | self.command_buffer_allocator.clone(), 127 | self.gfx_queue.queue_family_index(), 128 | CommandBufferUsage::MultipleSubmit, 129 | CommandBufferInheritanceInfo { 130 | render_pass: Some(self.subpass.clone().into()), 131 | ..Default::default() 132 | }, 133 | ) 134 | .unwrap(); 135 | builder 136 | .bind_pipeline_graphics(self.pipeline.clone()) 137 | .unwrap() 138 | .set_viewport( 139 | 0, 140 | [Viewport { 141 | offset: [0.0, 0.0], 142 | extent: [viewport_dimensions[0] as f32, viewport_dimensions[1] as f32], 143 | depth_range: 0.0..=1.0, 144 | }] 145 | .into_iter() 146 | .collect(), 147 | ) 148 | .unwrap() 149 | .bind_vertex_buffers(0, self.vertex_buffer.clone()) 150 | .unwrap(); 151 | unsafe { 152 | builder.draw(self.vertex_buffer.len() as u32, 1, 0, 0).unwrap(); 153 | } 154 | builder.build().unwrap() 155 | } 156 | } 157 | 158 | #[repr(C)] 159 | #[derive(BufferContents, Vertex)] 160 | struct MyVertex { 161 | #[format(R32G32_SFLOAT)] 162 | position: [f32; 2], 163 | #[format(R32G32B32A32_SFLOAT)] 164 | color: [f32; 4], 165 | } 166 | 167 | mod vs { 168 | vulkano_shaders::shader! { 169 | ty: "vertex", 170 | src: " 171 | #version 450 172 | layout(location = 0) in vec2 position; 173 | layout(location = 1) in vec4 color; 174 | 175 | layout(location = 0) out vec4 v_color; 176 | void main() { 177 | gl_Position = vec4(position, 0.0, 1.0); 178 | v_color = color; 179 | }" 180 | } 181 | } 182 | 183 | mod fs { 184 | vulkano_shaders::shader! { 185 | ty: "fragment", 186 | src: " 187 | #version 450 188 | layout(location = 0) in vec4 v_color; 189 | 190 | layout(location = 0) out vec4 f_color; 191 | 192 | void main() { 193 | f_color = v_color; 194 | }" 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /run_all_examples.ps1: -------------------------------------------------------------------------------- 1 | cargo build --examples --release 2 | cargo run --example wholesome --release 3 | cargo run --example minimal --release 4 | cargo run --example subpass --release 5 | cargo run --example demo_app --release 6 | cargo run --example paint_callback --release 7 | cargo run --example multisample --release 8 | -------------------------------------------------------------------------------- /run_all_examples.sh: -------------------------------------------------------------------------------- 1 | cargo build --examples --release 2 | cargo run --example wholesome --release 3 | cargo run --example minimal --release 4 | cargo run --example subpass --release 5 | cargo run --example demo_app --release 6 | cargo run --example paint_callback --release 7 | cargo run --example multisample --release 8 | -------------------------------------------------------------------------------- /run_checks.ps1: -------------------------------------------------------------------------------- 1 | cargo fmt -- --check --color always 2 | cargo clippy --all-targets -- -D warnings 3 | cargo test --workspace -------------------------------------------------------------------------------- /run_checks.sh: -------------------------------------------------------------------------------- 1 | cargo +nightly fmt -- --check --color always 2 | cargo +nightly clippy --all-targets -- -D warnings 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | comment_width = 120 2 | max_width = 100 3 | 4 | # Imports 5 | imports_granularity = "Crate" 6 | group_imports = "StdExternalCrate" 7 | 8 | format_strings = true 9 | newline_style = "Unix" 10 | overflow_delimited_expr = true 11 | use_small_heuristics = "Max" 12 | -------------------------------------------------------------------------------- /src/integration.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | use std::sync::Arc; 10 | 11 | use egui::{ClippedPrimitive, TexturesDelta}; 12 | use egui_winit::winit::event_loop::ActiveEventLoop; 13 | use vulkano::{ 14 | command_buffer::SecondaryAutoCommandBuffer, 15 | device::Queue, 16 | format::{Format, NumericFormat}, 17 | image::{sampler::SamplerCreateInfo, view::ImageView, SampleCount}, 18 | render_pass::Subpass, 19 | swapchain::Surface, 20 | sync::GpuFuture, 21 | }; 22 | use winit::window::Window; 23 | 24 | #[cfg(feature = "image")] 25 | use crate::utils::immutable_texture_from_file; 26 | use crate::{ 27 | renderer::{RenderResources, Renderer}, 28 | utils::immutable_texture_from_bytes, 29 | }; 30 | 31 | pub struct GuiConfig { 32 | /// Allows supplying sRGB ImageViews as render targets instead of just UNORM ImageViews, defaults to false. 33 | /// **Using sRGB will cause minor discoloration of UI elements** due to blending in linear color space and not 34 | /// sRGB as Egui expects. 35 | /// 36 | /// If you would like to visually compare between UNORM and sRGB render targets, run the `demo_app` example of 37 | /// this crate. 38 | pub allow_srgb_render_target: bool, 39 | /// Whether to render gui as overlay. Only relevant in the case of `Gui::new`, not when using 40 | /// subpass. Determines whether the pipeline should clear the target image. 41 | pub is_overlay: bool, 42 | /// Multisample count. Defaults to 1. If you use more than 1, you'll have to ensure your 43 | /// pipeline and target image matches that. 44 | pub samples: SampleCount, 45 | } 46 | 47 | impl Default for GuiConfig { 48 | fn default() -> Self { 49 | GuiConfig { 50 | allow_srgb_render_target: false, 51 | is_overlay: false, 52 | samples: SampleCount::Sample1, 53 | } 54 | } 55 | } 56 | 57 | impl GuiConfig { 58 | pub fn validate(&self, output_format: Format) { 59 | if output_format.numeric_format_color().unwrap() == NumericFormat::SRGB { 60 | assert!( 61 | self.allow_srgb_render_target, 62 | "Using an output format with sRGB requires `GuiConfig::allow_srgb_render_target` \ 63 | to be set! Egui prefers UNORM render targets. Using sRGB will cause minor \ 64 | discoloration of UI elements due to blending in linear color space and not sRGB \ 65 | as Egui expects." 66 | ); 67 | } 68 | } 69 | } 70 | 71 | pub struct Gui { 72 | pub egui_ctx: egui::Context, 73 | pub egui_winit: egui_winit::State, 74 | renderer: Renderer, 75 | surface: Arc, 76 | 77 | shapes: Vec, 78 | textures_delta: egui::TexturesDelta, 79 | } 80 | 81 | impl Gui { 82 | /// Creates new Egui to Vulkano integration by setting the necessary parameters 83 | /// This is to be called once we have access to vulkano_win's winit window surface 84 | /// and gfx queue. Created with this, the renderer will own a render pass which is useful to e.g. place your render pass' images 85 | /// onto egui windows 86 | pub fn new( 87 | event_loop: &ActiveEventLoop, 88 | surface: Arc, 89 | gfx_queue: Arc, 90 | output_format: Format, 91 | config: GuiConfig, 92 | ) -> Gui { 93 | config.validate(output_format); 94 | let renderer = Renderer::new_with_render_pass( 95 | gfx_queue, 96 | output_format, 97 | config.is_overlay, 98 | config.samples, 99 | ); 100 | Self::new_internal(event_loop, surface, renderer) 101 | } 102 | 103 | /// Same as `new` but instead of integration owning a render pass, egui renders on your subpass 104 | pub fn new_with_subpass( 105 | event_loop: &ActiveEventLoop, 106 | surface: Arc, 107 | gfx_queue: Arc, 108 | subpass: Subpass, 109 | output_format: Format, 110 | config: GuiConfig, 111 | ) -> Gui { 112 | config.validate(output_format); 113 | let renderer = Renderer::new_with_subpass(gfx_queue, output_format, subpass); 114 | Self::new_internal(event_loop, surface, renderer) 115 | } 116 | 117 | /// Same as `new` but instead of integration owning a render pass, egui renders on your subpass 118 | fn new_internal( 119 | event_loop: &ActiveEventLoop, 120 | surface: Arc, 121 | renderer: Renderer, 122 | ) -> Gui { 123 | let max_texture_side = 124 | renderer.queue().device().physical_device().properties().max_image_dimension2_d 125 | as usize; 126 | let egui_ctx: egui::Context = Default::default(); 127 | let theme = match egui_ctx.theme() { 128 | egui::Theme::Dark => winit::window::Theme::Dark, 129 | egui::Theme::Light => winit::window::Theme::Light, 130 | }; 131 | let egui_winit = egui_winit::State::new( 132 | egui_ctx.clone(), 133 | egui_ctx.viewport_id(), 134 | event_loop, 135 | Some(surface_window(&surface).scale_factor() as f32), 136 | Some(theme), 137 | Some(max_texture_side), 138 | ); 139 | Gui { 140 | egui_ctx, 141 | egui_winit, 142 | renderer, 143 | surface, 144 | shapes: vec![], 145 | textures_delta: Default::default(), 146 | } 147 | } 148 | 149 | /// Returns the pixels per point of the window of this gui. 150 | fn pixels_per_point(&self) -> f32 { 151 | egui_winit::pixels_per_point(&self.egui_ctx, surface_window(&self.surface)) 152 | } 153 | 154 | /// Returns a set of resources used to construct the render pipeline. These can be reused 155 | /// to create additional pipelines and buffers to be rendered in a `PaintCallback`. 156 | pub fn render_resources(&self) -> RenderResources { 157 | self.renderer.render_resources() 158 | } 159 | 160 | /// Updates context state by winit window event. 161 | /// Returns `true` if egui wants exclusive use of this event 162 | /// (e.g. a mouse click on an egui window, or entering text into a text field). 163 | /// For instance, if you use egui for a game, you want to first call this 164 | /// and only when this returns `false` pass on the events to your game. 165 | /// 166 | /// Note that egui uses `tab` to move focus between elements, so this will always return `true` for tabs. 167 | pub fn update(&mut self, winit_event: &winit::event::WindowEvent) -> bool { 168 | self.egui_winit.on_window_event(surface_window(&self.surface), winit_event).consumed 169 | } 170 | 171 | /// Begins Egui frame & determines what will be drawn later. This must be called before draw, and after `update` (winit event). 172 | pub fn immediate_ui(&mut self, layout_function: impl FnOnce(&mut Self)) { 173 | let raw_input = self.egui_winit.take_egui_input(surface_window(&self.surface)); 174 | self.egui_ctx.begin_pass(raw_input); 175 | // Render Egui 176 | layout_function(self); 177 | } 178 | 179 | /// If you wish to better control when to begin frame, do so by calling this function 180 | /// (Finish by drawing) 181 | pub fn begin_frame(&mut self) { 182 | let raw_input = self.egui_winit.take_egui_input(surface_window(&self.surface)); 183 | self.egui_ctx.begin_pass(raw_input); 184 | } 185 | 186 | /// Renders ui on `final_image` & Updates cursor icon 187 | /// Finishes Egui frame 188 | /// - `before_future` = Vulkano's GpuFuture 189 | /// - `final_image` = Vulkano's image (render target) 190 | pub fn draw_on_image( 191 | &mut self, 192 | before_future: F, 193 | final_image: Arc, 194 | ) -> Box 195 | where 196 | F: GpuFuture + 'static, 197 | { 198 | if !self.renderer.has_renderpass() { 199 | panic!( 200 | "Gui integration has been created with subpass, use `draw_on_subpass_image` \ 201 | instead" 202 | ) 203 | } 204 | 205 | let (clipped_meshes, textures_delta) = self.extract_draw_data_at_frame_end(); 206 | 207 | self.renderer.draw_on_image( 208 | &clipped_meshes, 209 | &textures_delta, 210 | self.pixels_per_point(), 211 | before_future, 212 | final_image, 213 | ) 214 | } 215 | 216 | /// Creates commands for rendering ui on subpass' image and returns the command buffer for execution on your side 217 | /// - Finishes Egui frame 218 | /// - You must execute the secondary command buffer yourself 219 | pub fn draw_on_subpass_image( 220 | &mut self, 221 | image_dimensions: [u32; 2], 222 | ) -> Arc { 223 | if self.renderer.has_renderpass() { 224 | panic!( 225 | "Gui integration has been created with its own render pass, use `draw_on_image` \ 226 | instead" 227 | ) 228 | } 229 | 230 | let (clipped_meshes, textures_delta) = self.extract_draw_data_at_frame_end(); 231 | 232 | self.renderer.draw_on_subpass_image( 233 | &clipped_meshes, 234 | &textures_delta, 235 | self.pixels_per_point(), 236 | image_dimensions, 237 | ) 238 | } 239 | 240 | fn extract_draw_data_at_frame_end(&mut self) -> (Vec, TexturesDelta) { 241 | self.end_frame(); 242 | let shapes = std::mem::take(&mut self.shapes); 243 | let textures_delta = std::mem::take(&mut self.textures_delta); 244 | let clipped_meshes = self.egui_ctx.tessellate(shapes, self.pixels_per_point()); 245 | (clipped_meshes, textures_delta) 246 | } 247 | 248 | fn end_frame(&mut self) { 249 | let egui::FullOutput { 250 | platform_output, 251 | textures_delta, 252 | shapes, 253 | pixels_per_point: _, 254 | viewport_output: _, 255 | } = self.egui_ctx.end_pass(); 256 | 257 | self.egui_winit.handle_platform_output(surface_window(&self.surface), platform_output); 258 | self.shapes = shapes; 259 | self.textures_delta = textures_delta; 260 | } 261 | 262 | /// Registers a user image from Vulkano image view to be used by egui 263 | pub fn register_user_image_view( 264 | &mut self, 265 | image: Arc, 266 | sampler_create_info: SamplerCreateInfo, 267 | ) -> egui::TextureId { 268 | self.renderer.register_image(image, sampler_create_info) 269 | } 270 | 271 | /// Registers a user image to be used by egui 272 | /// - `image_file_bytes`: e.g. include_bytes!("./assets/tree.png") 273 | /// - `format`: e.g. vulkano::format::Format::R8G8B8A8Unorm 274 | #[cfg(feature = "image")] 275 | pub fn register_user_image( 276 | &mut self, 277 | image_file_bytes: &[u8], 278 | format: vulkano::format::Format, 279 | sampler_create_info: SamplerCreateInfo, 280 | ) -> egui::TextureId { 281 | let image = immutable_texture_from_file( 282 | self.renderer.allocators(), 283 | self.renderer.queue(), 284 | image_file_bytes, 285 | format, 286 | ) 287 | .expect("Failed to create image"); 288 | self.renderer.register_image(image, sampler_create_info) 289 | } 290 | 291 | pub fn register_user_image_from_bytes( 292 | &mut self, 293 | image_byte_data: &[u8], 294 | dimensions: [u32; 2], 295 | format: vulkano::format::Format, 296 | sampler_create_info: SamplerCreateInfo, 297 | ) -> egui::TextureId { 298 | let image = immutable_texture_from_bytes( 299 | self.renderer.allocators(), 300 | self.renderer.queue(), 301 | image_byte_data, 302 | dimensions, 303 | format, 304 | ) 305 | .expect("Failed to create image"); 306 | self.renderer.register_image(image, sampler_create_info) 307 | } 308 | 309 | /// Unregisters a user image 310 | pub fn unregister_user_image(&mut self, texture_id: egui::TextureId) { 311 | self.renderer.unregister_image(texture_id); 312 | } 313 | 314 | /// Access egui's context (which can be used to e.g. set fonts, visuals etc) 315 | pub fn context(&self) -> egui::Context { 316 | self.egui_ctx.clone() 317 | } 318 | } 319 | 320 | // Helper to retrieve Window from surface object 321 | fn surface_window(surface: &Surface) -> &Window { 322 | surface.object().unwrap().downcast_ref::().unwrap() 323 | } 324 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | mod integration; 11 | mod renderer; 12 | mod utils; 13 | 14 | pub use egui; 15 | pub use integration::*; 16 | pub use renderer::{CallbackContext, CallbackFn, RenderResources}; 17 | pub use utils::immutable_texture_from_bytes; 18 | #[cfg(feature = "image")] 19 | pub use utils::immutable_texture_from_file; 20 | -------------------------------------------------------------------------------- /src/renderer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | use std::sync::Arc; 11 | 12 | use ahash::AHashMap; 13 | use egui::{epaint::Primitive, ClippedPrimitive, PaintCallbackInfo, Rect, TexturesDelta}; 14 | use vulkano::{ 15 | buffer::{ 16 | allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, 17 | Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, 18 | }, 19 | command_buffer::{ 20 | allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, BufferImageCopy, 21 | CommandBufferInheritanceInfo, CommandBufferUsage, CopyBufferToImageInfo, 22 | PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract, RenderPassBeginInfo, 23 | SecondaryAutoCommandBuffer, SubpassBeginInfo, SubpassContents, 24 | }, 25 | descriptor_set::{ 26 | allocator::StandardDescriptorSetAllocator, layout::DescriptorSetLayout, DescriptorSet, 27 | WriteDescriptorSet, 28 | }, 29 | device::Queue, 30 | format::{Format, NumericFormat}, 31 | image::{ 32 | sampler::{ 33 | ComponentMapping, ComponentSwizzle, Filter, Sampler, SamplerAddressMode, 34 | SamplerCreateInfo, SamplerMipmapMode, 35 | }, 36 | view::{ImageView, ImageViewCreateInfo}, 37 | Image, ImageAspects, ImageCreateInfo, ImageLayout, ImageSubresourceLayers, ImageType, 38 | ImageUsage, SampleCount, 39 | }, 40 | memory::{ 41 | allocator::{ 42 | AllocationCreateInfo, DeviceLayout, MemoryTypeFilter, StandardMemoryAllocator, 43 | }, 44 | DeviceAlignment, 45 | }, 46 | pipeline::{ 47 | graphics::{ 48 | color_blend::{ 49 | AttachmentBlend, BlendFactor, ColorBlendAttachmentState, ColorBlendState, 50 | }, 51 | depth_stencil::{CompareOp, DepthState, DepthStencilState}, 52 | input_assembly::InputAssemblyState, 53 | multisample::MultisampleState, 54 | rasterization::RasterizationState, 55 | vertex_input::{Vertex, VertexDefinition}, 56 | viewport::{Scissor, Viewport, ViewportState}, 57 | GraphicsPipelineCreateInfo, 58 | }, 59 | layout::PipelineDescriptorSetLayoutCreateInfo, 60 | DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, 61 | PipelineShaderStageCreateInfo, 62 | }, 63 | render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, 64 | sync::GpuFuture, 65 | DeviceSize, NonZeroDeviceSize, 66 | }; 67 | 68 | use crate::utils::Allocators; 69 | 70 | const VERTICES_PER_QUAD: DeviceSize = 4; 71 | const VERTEX_BUFFER_SIZE: DeviceSize = 1024 * 1024 * VERTICES_PER_QUAD; 72 | const INDEX_BUFFER_SIZE: DeviceSize = 1024 * 1024 * 2; 73 | 74 | type VertexBuffer = Subbuffer<[egui::epaint::Vertex]>; 75 | type IndexBuffer = Subbuffer<[u32]>; 76 | 77 | /// Should match vertex definition of egui 78 | #[repr(C)] 79 | #[derive(BufferContents, Vertex)] 80 | pub struct EguiVertex { 81 | #[format(R32G32_SFLOAT)] 82 | pub position: [f32; 2], 83 | #[format(R32G32_SFLOAT)] 84 | pub tex_coords: [f32; 2], 85 | #[format(R8G8B8A8_UNORM)] 86 | pub color: [u8; 4], 87 | } 88 | 89 | pub struct Renderer { 90 | gfx_queue: Arc, 91 | render_pass: Option>, 92 | is_overlay: bool, 93 | output_in_linear_colorspace: bool, 94 | 95 | #[allow(unused)] 96 | format: vulkano::format::Format, 97 | font_sampler: Arc, 98 | // May be R8G8_UNORM or R8G8B8A8_SRGB 99 | font_format: Format, 100 | 101 | allocators: Allocators, 102 | vertex_index_buffer_pool: SubbufferAllocator, 103 | pipeline: Arc, 104 | subpass: Subpass, 105 | 106 | texture_desc_sets: AHashMap>, 107 | texture_images: AHashMap>, 108 | next_native_tex_id: u64, 109 | } 110 | 111 | impl Renderer { 112 | pub fn new_with_subpass( 113 | gfx_queue: Arc, 114 | final_output_format: Format, 115 | subpass: Subpass, 116 | ) -> Renderer { 117 | Self::new_internal(gfx_queue, final_output_format, subpass, None, false) 118 | } 119 | 120 | /// Creates a new [Renderer] which is responsible for rendering egui with its own renderpass 121 | /// See examples 122 | pub fn new_with_render_pass( 123 | gfx_queue: Arc, 124 | final_output_format: Format, 125 | is_overlay: bool, 126 | samples: SampleCount, 127 | ) -> Renderer { 128 | // Create Gui render pass with just depth and final color 129 | let render_pass = if is_overlay { 130 | vulkano::single_pass_renderpass!(gfx_queue.device().clone(), 131 | attachments: { 132 | final_color: { 133 | format: final_output_format, 134 | samples: samples, 135 | load_op: Load, 136 | store_op: Store, 137 | } 138 | }, 139 | pass: { 140 | color: [final_color], 141 | depth_stencil: {} 142 | } 143 | ) 144 | .unwrap() 145 | } else { 146 | vulkano::single_pass_renderpass!(gfx_queue.device().clone(), 147 | attachments: { 148 | final_color: { 149 | format: final_output_format, 150 | samples: samples, 151 | load_op: Clear, 152 | store_op: Store, 153 | } 154 | }, 155 | pass: { 156 | color: [final_color], 157 | depth_stencil: {} 158 | } 159 | ) 160 | .unwrap() 161 | }; 162 | let subpass = Subpass::from(render_pass.clone(), 0).unwrap(); 163 | Self::new_internal(gfx_queue, final_output_format, subpass, Some(render_pass), is_overlay) 164 | } 165 | 166 | fn new_internal( 167 | gfx_queue: Arc, 168 | final_output_format: Format, 169 | subpass: Subpass, 170 | render_pass: Option>, 171 | is_overlay: bool, 172 | ) -> Renderer { 173 | let output_in_linear_colorspace = 174 | // final_output_format.type_color().unwrap() == NumericType::SRGB; 175 | final_output_format.numeric_format_color().unwrap() == NumericFormat::SRGB; 176 | let allocators = Allocators::new_default(gfx_queue.device()); 177 | let vertex_index_buffer_pool = 178 | SubbufferAllocator::new(allocators.memory.clone(), SubbufferAllocatorCreateInfo { 179 | arena_size: INDEX_BUFFER_SIZE + VERTEX_BUFFER_SIZE, 180 | buffer_usage: BufferUsage::INDEX_BUFFER | BufferUsage::VERTEX_BUFFER, 181 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 182 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 183 | ..Default::default() 184 | }); 185 | let pipeline = Self::create_pipeline(gfx_queue.clone(), subpass.clone()); 186 | let font_sampler = Sampler::new(gfx_queue.device().clone(), SamplerCreateInfo { 187 | mag_filter: Filter::Linear, 188 | min_filter: Filter::Linear, 189 | address_mode: [SamplerAddressMode::ClampToEdge; 3], 190 | mipmap_mode: SamplerMipmapMode::Linear, 191 | ..Default::default() 192 | }) 193 | .unwrap(); 194 | let font_format = Self::choose_font_format(gfx_queue.device()); 195 | Renderer { 196 | gfx_queue, 197 | format: final_output_format, 198 | render_pass, 199 | vertex_index_buffer_pool, 200 | pipeline, 201 | subpass, 202 | texture_desc_sets: AHashMap::default(), 203 | texture_images: AHashMap::default(), 204 | next_native_tex_id: 0, 205 | is_overlay, 206 | output_in_linear_colorspace, 207 | font_sampler, 208 | font_format, 209 | allocators, 210 | } 211 | } 212 | 213 | pub fn has_renderpass(&self) -> bool { 214 | self.render_pass.is_some() 215 | } 216 | 217 | fn create_pipeline(gfx_queue: Arc, subpass: Subpass) -> Arc { 218 | let vs = vs::load(gfx_queue.device().clone()) 219 | .expect("failed to create shader module") 220 | .entry_point("main") 221 | .unwrap(); 222 | let fs = fs::load(gfx_queue.device().clone()) 223 | .expect("failed to create shader module") 224 | .entry_point("main") 225 | .unwrap(); 226 | 227 | let mut blend = AttachmentBlend::alpha(); 228 | blend.src_color_blend_factor = BlendFactor::One; 229 | blend.src_alpha_blend_factor = BlendFactor::OneMinusDstAlpha; 230 | blend.dst_alpha_blend_factor = BlendFactor::One; 231 | let blend_state = ColorBlendState { 232 | attachments: vec![ColorBlendAttachmentState { 233 | blend: Some(blend), 234 | ..Default::default() 235 | }], 236 | ..ColorBlendState::default() 237 | }; 238 | 239 | let has_depth_buffer = subpass 240 | .subpass_desc() 241 | .depth_stencil_attachment 242 | .as_ref() 243 | .is_some_and(|depth_stencil_attachment| { 244 | subpass.render_pass().attachments()[depth_stencil_attachment.attachment as usize] 245 | .format 246 | .aspects() 247 | .intersects(ImageAspects::DEPTH) 248 | }); 249 | let depth_stencil_state = if has_depth_buffer { 250 | Some(DepthStencilState { 251 | depth: Some(DepthState { write_enable: false, compare_op: CompareOp::Always }), 252 | ..Default::default() 253 | }) 254 | } else { 255 | None 256 | }; 257 | 258 | let vertex_input_state = Some(EguiVertex::per_vertex().definition(&vs).unwrap()); 259 | 260 | let stages = 261 | [PipelineShaderStageCreateInfo::new(vs), PipelineShaderStageCreateInfo::new(fs)]; 262 | 263 | let layout = PipelineLayout::new( 264 | gfx_queue.device().clone(), 265 | PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) 266 | .into_pipeline_layout_create_info(gfx_queue.device().clone()) 267 | .unwrap(), 268 | ) 269 | .unwrap(); 270 | 271 | GraphicsPipeline::new(gfx_queue.device().clone(), None, GraphicsPipelineCreateInfo { 272 | stages: stages.into_iter().collect(), 273 | vertex_input_state, 274 | input_assembly_state: Some(InputAssemblyState::default()), 275 | viewport_state: Some(ViewportState::default()), 276 | rasterization_state: Some(RasterizationState::default()), 277 | multisample_state: Some(MultisampleState { 278 | rasterization_samples: subpass.num_samples().unwrap_or(SampleCount::Sample1), 279 | ..Default::default() 280 | }), 281 | color_blend_state: Some(blend_state), 282 | depth_stencil_state, 283 | dynamic_state: [DynamicState::Viewport, DynamicState::Scissor].into_iter().collect(), 284 | subpass: Some(subpass.into()), 285 | ..GraphicsPipelineCreateInfo::layout(layout) 286 | }) 287 | .unwrap() 288 | } 289 | 290 | /// Creates a descriptor set for images 291 | fn sampled_image_desc_set( 292 | &self, 293 | layout: &Arc, 294 | image: Arc, 295 | sampler: Arc, 296 | ) -> Arc { 297 | DescriptorSet::new( 298 | self.allocators.descriptor_set.clone(), 299 | layout.clone(), 300 | [WriteDescriptorSet::image_view_sampler(0, image, sampler)], 301 | [], 302 | ) 303 | .unwrap() 304 | } 305 | 306 | /// Registers a user texture. User texture needs to be unregistered when it is no longer needed 307 | pub fn register_image( 308 | &mut self, 309 | image: Arc, 310 | sampler_create_info: SamplerCreateInfo, 311 | ) -> egui::TextureId { 312 | let layout = self.pipeline.layout().set_layouts().first().unwrap(); 313 | let sampler = Sampler::new(self.gfx_queue.device().clone(), sampler_create_info).unwrap(); 314 | let desc_set = self.sampled_image_desc_set(layout, image.clone(), sampler); 315 | let id = egui::TextureId::User(self.next_native_tex_id); 316 | self.next_native_tex_id += 1; 317 | self.texture_desc_sets.insert(id, desc_set); 318 | self.texture_images.insert(id, image); 319 | id 320 | } 321 | 322 | /// Unregister user texture. 323 | pub fn unregister_image(&mut self, texture_id: egui::TextureId) { 324 | self.texture_desc_sets.remove(&texture_id); 325 | self.texture_images.remove(&texture_id); 326 | } 327 | /// Choose a font format, attempt to minimize memory footprint and CPU unpacking time 328 | /// by choosing a swizzled linear format. 329 | fn choose_font_format(device: &vulkano::device::Device) -> Format { 330 | // Some portability subset devices are unable to swizzle views. 331 | let supports_swizzle = 332 | !device.physical_device().supported_extensions().khr_portability_subset 333 | || device.physical_device().supported_features().image_view_format_swizzle; 334 | // Check that this format is supported for all our uses: 335 | let is_supported = |device: &vulkano::device::Device, format: Format| { 336 | device 337 | .physical_device() 338 | .image_format_properties(vulkano::image::ImageFormatInfo { 339 | format, 340 | usage: ImageUsage::SAMPLED 341 | | ImageUsage::TRANSFER_DST 342 | | ImageUsage::TRANSFER_SRC, 343 | ..Default::default() 344 | }) 345 | // Ok(Some(..)) is supported format for this usage. 346 | .is_ok_and(|properties| properties.is_some()) 347 | }; 348 | if supports_swizzle && is_supported(device, Format::R8G8_UNORM) { 349 | // We can save mem by swizzling in hardware! 350 | Format::R8G8_UNORM 351 | } else { 352 | // Rest of implementation assumes R8G8B8A8_SRGB anyway! 353 | Format::R8G8B8A8_SRGB 354 | } 355 | } 356 | /// Based on self.font_format, extract into bytes. 357 | fn pack_font_data_into(&self, data: &egui::FontImage, into: &mut [u8]) { 358 | match self.font_format { 359 | Format::R8G8_UNORM => { 360 | // Egui expects RGB to be linear in shader, but alpha to be *nonlinear.* 361 | // Thus, we use R channel for linear coverage, G for the same coverage converted to nonlinear. 362 | // Then gets swizzled up to RRRG to match expected values. 363 | let linear = 364 | data.pixels.iter().map(|f| (f.clamp(0.0, 1.0 - f32::EPSILON) * 256.0) as u8); 365 | let bytes = linear 366 | .zip(data.srgba_pixels(None)) 367 | .flat_map(|(linear, srgb)| [linear, srgb.a()]); 368 | 369 | into.iter_mut().zip(bytes).for_each(|(into, from)| *into = from); 370 | } 371 | Format::R8G8B8A8_SRGB => { 372 | // No special tricks, pack them directly. 373 | let bytes = data.srgba_pixels(None).flat_map(|color| color.to_array()); 374 | into.iter_mut().zip(bytes).for_each(|(into, from)| *into = from); 375 | } 376 | // This is the exhaustive list of choosable font formats. 377 | _ => unreachable!(), 378 | } 379 | } 380 | fn image_size_bytes(&self, delta: &egui::epaint::ImageDelta) -> usize { 381 | match &delta.image { 382 | egui::ImageData::Color(c) => { 383 | // Always four bytes per pixel for sRGBA 384 | c.width() * c.height() * 4 385 | } 386 | egui::ImageData::Font(f) => { 387 | f.width() 388 | * f.height() 389 | * match self.font_format { 390 | Format::R8G8_UNORM => 2, 391 | Format::R8G8B8A8_SRGB => 4, 392 | // Exhaustive list of valid font formats 393 | _ => unreachable!(), 394 | } 395 | } 396 | } 397 | } 398 | /// Write a single texture delta using the provided staging region and commandbuffer 399 | fn update_texture_within( 400 | &mut self, 401 | id: egui::TextureId, 402 | delta: &egui::epaint::ImageDelta, 403 | stage: Subbuffer<[u8]>, 404 | mapped_stage: &mut [u8], 405 | cbb: &mut AutoCommandBufferBuilder, 406 | ) { 407 | // Extract pixel data from egui, writing into our region of the stage buffer. 408 | let format = match &delta.image { 409 | egui::ImageData::Color(image) => { 410 | assert_eq!( 411 | image.width() * image.height(), 412 | image.pixels.len(), 413 | "Mismatch between texture size and texel count" 414 | ); 415 | let bytes = image.pixels.iter().flat_map(|color| color.to_array()); 416 | mapped_stage.iter_mut().zip(bytes).for_each(|(into, from)| *into = from); 417 | Format::R8G8B8A8_SRGB 418 | } 419 | egui::ImageData::Font(image) => { 420 | // Dynamically pack based on chosen format 421 | self.pack_font_data_into(image, mapped_stage); 422 | self.font_format 423 | } 424 | }; 425 | 426 | // Copy texture data to existing image if delta pos exists (e.g. font changed) 427 | if let Some(pos) = delta.pos { 428 | let Some(existing_image) = self.texture_images.get(&id) else { 429 | // Egui wants us to update this texture but we don't have it to begin with! 430 | panic!("attempt to write into non-existing image"); 431 | }; 432 | // Make sure delta image type and destination image type match. 433 | assert_eq!(existing_image.format(), format); 434 | 435 | // Defer upload of data 436 | cbb.copy_buffer_to_image(CopyBufferToImageInfo { 437 | regions: [BufferImageCopy { 438 | // Buffer offsets are derived 439 | image_offset: [pos[0] as u32, pos[1] as u32, 0], 440 | image_extent: [delta.image.width() as u32, delta.image.height() as u32, 1], 441 | // Always use the whole image (no arrays or mips are performed) 442 | image_subresource: ImageSubresourceLayers { 443 | aspects: ImageAspects::COLOR, 444 | mip_level: 0, 445 | array_layers: 0..1, 446 | }, 447 | ..Default::default() 448 | }] 449 | .into(), 450 | ..CopyBufferToImageInfo::buffer_image(stage, existing_image.image().clone()) 451 | }) 452 | .unwrap(); 453 | } else { 454 | // Otherwise save the newly created image 455 | let img = { 456 | let extent = [delta.image.width() as u32, delta.image.height() as u32, 1]; 457 | Image::new( 458 | self.allocators.memory.clone(), 459 | ImageCreateInfo { 460 | image_type: ImageType::Dim2d, 461 | format, 462 | extent, 463 | usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED, 464 | initial_layout: ImageLayout::Undefined, 465 | ..Default::default() 466 | }, 467 | AllocationCreateInfo::default(), 468 | ) 469 | .unwrap() 470 | }; 471 | // Defer upload of data 472 | cbb.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(stage, img.clone())) 473 | .unwrap(); 474 | // Swizzle packed font images up to a full premul white. 475 | let component_mapping = match format { 476 | Format::R8G8_UNORM => ComponentMapping { 477 | r: ComponentSwizzle::Red, 478 | g: ComponentSwizzle::Red, 479 | b: ComponentSwizzle::Red, 480 | a: ComponentSwizzle::Green, 481 | }, 482 | _ => ComponentMapping::identity(), 483 | }; 484 | let view = ImageView::new(img.clone(), ImageViewCreateInfo { 485 | component_mapping, 486 | ..ImageViewCreateInfo::from_image(&img) 487 | }) 488 | .unwrap(); 489 | // Create a descriptor for it 490 | let layout = self.pipeline.layout().set_layouts().first().unwrap(); 491 | let desc_set = 492 | self.sampled_image_desc_set(layout, view.clone(), self.font_sampler.clone()); 493 | // Save! 494 | self.texture_desc_sets.insert(id, desc_set); 495 | self.texture_images.insert(id, view); 496 | }; 497 | } 498 | /// Write the entire texture delta for this frame. 499 | fn update_textures(&mut self, sets: &[(egui::TextureId, egui::epaint::ImageDelta)]) { 500 | // Allocate enough memory to upload every delta at once. 501 | let total_size_bytes = 502 | sets.iter().map(|(_, set)| self.image_size_bytes(set)).sum::() * 4; 503 | // Infallible - unless we're on a 128 bit machine? :P 504 | let total_size_bytes = u64::try_from(total_size_bytes).unwrap(); 505 | let Ok(total_size_bytes) = vulkano::NonZeroDeviceSize::try_from(total_size_bytes) else { 506 | // Nothing to upload! 507 | return; 508 | }; 509 | let buffer = Buffer::new( 510 | self.allocators.memory.clone(), 511 | BufferCreateInfo { usage: BufferUsage::TRANSFER_SRC, ..Default::default() }, 512 | AllocationCreateInfo { 513 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 514 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 515 | ..Default::default() 516 | }, 517 | // Bytes, align of one, infallible. 518 | DeviceLayout::new(total_size_bytes, DeviceAlignment::MIN).unwrap(), 519 | ) 520 | .unwrap(); 521 | let buffer = Subbuffer::new(buffer); 522 | 523 | // Shared command buffer for every upload in this batch. 524 | let mut cbb = AutoCommandBufferBuilder::primary( 525 | self.allocators.command_buffer.clone(), 526 | self.gfx_queue.queue_family_index(), 527 | CommandBufferUsage::OneTimeSubmit, 528 | ) 529 | .unwrap(); 530 | 531 | { 532 | // Scoped to keep writer lock bounded 533 | // Should be infallible - Just made the buffer so it's exclusive, and we have host access to it. 534 | let mut writer = buffer.write().unwrap(); 535 | 536 | // Keep track of where to write the next image to into the staging buffer. 537 | let mut past_buffer_end = 0usize; 538 | 539 | for (id, delta) in sets { 540 | let image_size_bytes = self.image_size_bytes(delta); 541 | let range = past_buffer_end..(image_size_bytes + past_buffer_end); 542 | 543 | // Bump for next loop 544 | past_buffer_end += image_size_bytes; 545 | 546 | // Represents the same memory in two ways. Writable memmap, and gpu-side description. 547 | let stage = buffer.clone().slice(range.start as u64..range.end as u64); 548 | let mapped_stage = &mut writer[range]; 549 | 550 | self.update_texture_within(*id, delta, stage, mapped_stage, &mut cbb); 551 | } 552 | } 553 | 554 | // Execute every upload at once and await: 555 | let command_buffer = cbb.build().unwrap(); 556 | // Executing on the graphics queue not only since it's what we have, but 557 | // we must guarantee a transfer granularity of [1,1,x] which graphics queue is required to have. 558 | command_buffer 559 | .execute(self.gfx_queue.clone()) 560 | .unwrap() 561 | .then_signal_fence_and_flush() 562 | .unwrap() 563 | .wait(None) 564 | .unwrap(); 565 | } 566 | 567 | fn get_rect_scissor( 568 | &self, 569 | scale_factor: f32, 570 | framebuffer_dimensions: [u32; 2], 571 | rect: Rect, 572 | ) -> Scissor { 573 | let min = rect.min; 574 | let min = egui::Pos2 { x: min.x * scale_factor, y: min.y * scale_factor }; 575 | let min = egui::Pos2 { 576 | x: min.x.clamp(0.0, framebuffer_dimensions[0] as f32), 577 | y: min.y.clamp(0.0, framebuffer_dimensions[1] as f32), 578 | }; 579 | let max = rect.max; 580 | let max = egui::Pos2 { x: max.x * scale_factor, y: max.y * scale_factor }; 581 | let max = egui::Pos2 { 582 | x: max.x.clamp(min.x, framebuffer_dimensions[0] as f32), 583 | y: max.y.clamp(min.y, framebuffer_dimensions[1] as f32), 584 | }; 585 | Scissor { 586 | offset: [min.x.round() as u32, min.y.round() as u32], 587 | extent: [(max.x.round() - min.x) as u32, (max.y.round() - min.y) as u32], 588 | } 589 | } 590 | 591 | fn create_secondary_command_buffer_builder( 592 | &self, 593 | ) -> AutoCommandBufferBuilder { 594 | AutoCommandBufferBuilder::secondary( 595 | self.allocators.command_buffer.clone(), 596 | self.gfx_queue.queue_family_index(), 597 | CommandBufferUsage::MultipleSubmit, 598 | CommandBufferInheritanceInfo { 599 | render_pass: Some(self.subpass.clone().into()), 600 | ..Default::default() 601 | }, 602 | ) 603 | .unwrap() 604 | } 605 | 606 | // Starts the rendering pipeline and returns [`RecordingCommandBuffer`] for drawing 607 | fn start( 608 | &mut self, 609 | final_image: Arc, 610 | ) -> (AutoCommandBufferBuilder, [u32; 2]) { 611 | // Get dimensions 612 | let img_dims = final_image.image().extent(); 613 | // Create framebuffer (must be in same order as render pass description in `new` 614 | let framebuffer = Framebuffer::new( 615 | self.render_pass 616 | .as_ref() 617 | .expect( 618 | "No renderpass on this renderer (created with subpass), use 'draw_subpass' \ 619 | instead", 620 | ) 621 | .clone(), 622 | FramebufferCreateInfo { attachments: vec![final_image], ..Default::default() }, 623 | ) 624 | .unwrap(); 625 | let mut command_buffer_builder = AutoCommandBufferBuilder::primary( 626 | self.allocators.command_buffer.clone(), 627 | self.gfx_queue.queue_family_index(), 628 | CommandBufferUsage::OneTimeSubmit, 629 | ) 630 | .unwrap(); 631 | // Add clear values here for attachments and begin render pass 632 | command_buffer_builder 633 | .begin_render_pass( 634 | RenderPassBeginInfo { 635 | clear_values: vec![if !self.is_overlay { Some([0.0; 4].into()) } else { None }], 636 | ..RenderPassBeginInfo::framebuffer(framebuffer) 637 | }, 638 | SubpassBeginInfo { 639 | contents: SubpassContents::SecondaryCommandBuffers, 640 | ..SubpassBeginInfo::default() 641 | }, 642 | ) 643 | .unwrap(); 644 | (command_buffer_builder, [img_dims[0], img_dims[1]]) 645 | } 646 | 647 | /// Executes our draw commands on the final image and returns a `GpuFuture` to wait on 648 | pub fn draw_on_image( 649 | &mut self, 650 | clipped_meshes: &[ClippedPrimitive], 651 | textures_delta: &TexturesDelta, 652 | scale_factor: f32, 653 | before_future: F, 654 | final_image: Arc, 655 | ) -> Box 656 | where 657 | F: GpuFuture + 'static, 658 | { 659 | self.update_textures(&textures_delta.set); 660 | 661 | let (mut command_buffer_builder, framebuffer_dimensions) = self.start(final_image); 662 | let mut builder = self.create_secondary_command_buffer_builder(); 663 | self.draw_egui(scale_factor, clipped_meshes, framebuffer_dimensions, &mut builder); 664 | // Execute draw commands 665 | let command_buffer = builder.build().unwrap(); 666 | command_buffer_builder.execute_commands(command_buffer).unwrap(); 667 | let done_future = self.finish(command_buffer_builder, Box::new(before_future)); 668 | 669 | for &id in &textures_delta.free { 670 | self.unregister_image(id); 671 | } 672 | 673 | done_future 674 | } 675 | 676 | // Finishes the rendering pipeline 677 | fn finish( 678 | &self, 679 | mut command_buffer_builder: AutoCommandBufferBuilder, 680 | before_main_cb_future: Box, 681 | ) -> Box { 682 | // We end render pass 683 | command_buffer_builder.end_render_pass(Default::default()).unwrap(); 684 | // Then execute our whole command buffer 685 | let command_buffer = command_buffer_builder.build().unwrap(); 686 | let after_main_cb = 687 | before_main_cb_future.then_execute(self.gfx_queue.clone(), command_buffer).unwrap(); 688 | // Return our future 689 | Box::new(after_main_cb) 690 | } 691 | 692 | pub fn draw_on_subpass_image( 693 | &mut self, 694 | clipped_meshes: &[ClippedPrimitive], 695 | textures_delta: &TexturesDelta, 696 | scale_factor: f32, 697 | framebuffer_dimensions: [u32; 2], 698 | ) -> Arc { 699 | self.update_textures(&textures_delta.set); 700 | let mut builder = self.create_secondary_command_buffer_builder(); 701 | self.draw_egui(scale_factor, clipped_meshes, framebuffer_dimensions, &mut builder); 702 | let buffer = builder.build().unwrap(); 703 | for &id in &textures_delta.free { 704 | self.unregister_image(id); 705 | } 706 | buffer 707 | } 708 | /// Uploads all meshes in bulk. They will be available in the same order, packed. 709 | /// None if no vertices or no indices. 710 | fn upload_meshes( 711 | &mut self, 712 | clipped_meshes: &[ClippedPrimitive], 713 | ) -> Option<(VertexBuffer, IndexBuffer)> { 714 | use egui::epaint::Vertex; 715 | type Index = u32; 716 | const VERTEX_ALIGN: DeviceAlignment = DeviceAlignment::of::(); 717 | const INDEX_ALIGN: DeviceAlignment = DeviceAlignment::of::(); 718 | 719 | // Iterator over only the meshes, no user callbacks. 720 | let meshes = clipped_meshes.iter().filter_map(|mesh| match &mesh.primitive { 721 | Primitive::Mesh(m) => Some(m), 722 | _ => None, 723 | }); 724 | 725 | // Calculate counts of each mesh, and total bytes for combined data 726 | let (total_vertices, total_size_bytes) = { 727 | let mut total_vertices = 0; 728 | let mut total_indices = 0; 729 | 730 | for mesh in meshes.clone() { 731 | total_vertices += mesh.vertices.len(); 732 | total_indices += mesh.indices.len(); 733 | } 734 | if total_indices == 0 || total_vertices == 0 { 735 | return None; 736 | } 737 | 738 | let total_size_bytes = total_vertices * std::mem::size_of::() 739 | + total_indices * std::mem::size_of::(); 740 | ( 741 | total_vertices, 742 | // Infallible! Checked above. 743 | NonZeroDeviceSize::new(u64::try_from(total_size_bytes).unwrap()).unwrap(), 744 | ) 745 | }; 746 | 747 | // Allocate a buffer which can hold both packed arrays: 748 | let layout = DeviceLayout::new(total_size_bytes, VERTEX_ALIGN.max(INDEX_ALIGN)).unwrap(); 749 | let buffer = self.vertex_index_buffer_pool.allocate(layout).unwrap(); 750 | 751 | // We must put the items with stricter align *first* in the packed buffer. 752 | // Correct at time of writing, but assert in case that changes. 753 | assert!(VERTEX_ALIGN >= INDEX_ALIGN); 754 | let (vertices, indices) = { 755 | let partition_bytes = total_vertices as u64 * std::mem::size_of::() as u64; 756 | ( 757 | // Slice the start as vertices 758 | buffer.clone().slice(..partition_bytes).reinterpret::<[Vertex]>(), 759 | // Take the rest, reinterpret as indices. 760 | buffer.slice(partition_bytes..).reinterpret::<[Index]>(), 761 | ) 762 | }; 763 | 764 | // We have to upload in two mapping steps to avoid trivial but ugly unsafe. 765 | { 766 | let mut vertex_write = vertices.write().unwrap(); 767 | vertex_write 768 | .iter_mut() 769 | .zip(meshes.clone().flat_map(|m| &m.vertices).copied()) 770 | .for_each(|(into, from)| *into = from); 771 | } 772 | { 773 | let mut index_write = indices.write().unwrap(); 774 | index_write 775 | .iter_mut() 776 | .zip(meshes.flat_map(|m| &m.indices).copied()) 777 | .for_each(|(into, from)| *into = from); 778 | } 779 | 780 | Some((vertices, indices)) 781 | } 782 | 783 | fn draw_egui( 784 | &mut self, 785 | scale_factor: f32, 786 | clipped_meshes: &[ClippedPrimitive], 787 | framebuffer_dimensions: [u32; 2], 788 | builder: &mut AutoCommandBufferBuilder, 789 | ) { 790 | let push_constants = vs::PushConstants { 791 | screen_size: [ 792 | framebuffer_dimensions[0] as f32 / scale_factor, 793 | framebuffer_dimensions[1] as f32 / scale_factor, 794 | ], 795 | output_in_linear_colorspace: self.output_in_linear_colorspace.into(), 796 | }; 797 | 798 | let mesh_buffers = self.upload_meshes(clipped_meshes); 799 | 800 | // Current position of renderbuffers, advances as meshes are consumed. 801 | let mut vertex_cursor = 0; 802 | let mut index_cursor = 0; 803 | // Some of our state is immutable and only changes 804 | // if a user callback thrashes it, rebind all when this is set: 805 | let mut needs_full_rebind = true; 806 | // Track resources that change from call-to-call. 807 | // egui already makes the optimization that draws with identical resources are merged into one, 808 | // so every mesh changes usually one or possibly both of these. 809 | let mut current_rect = None; 810 | let mut current_texture = None; 811 | 812 | for ClippedPrimitive { clip_rect, primitive } in clipped_meshes { 813 | match primitive { 814 | Primitive::Mesh(mesh) => { 815 | // Nothing to draw if we don't have vertices & indices 816 | if mesh.vertices.is_empty() || mesh.indices.is_empty() { 817 | // Consume the mesh and skip it. 818 | index_cursor += mesh.indices.len() as u32; 819 | vertex_cursor += mesh.vertices.len() as u32; 820 | continue; 821 | } 822 | // Reset overall state, if needed. 823 | // Only happens on first mesh, and after a user callback which does unknowable 824 | // things to the command buffer's state. 825 | if needs_full_rebind { 826 | needs_full_rebind = false; 827 | 828 | // Bind combined meshes. 829 | let Some((vertices, indices)) = mesh_buffers.clone() else { 830 | // Only None if there are no mesh calls, but here we are in a mesh call! 831 | unreachable!() 832 | }; 833 | 834 | builder 835 | .bind_pipeline_graphics(self.pipeline.clone()) 836 | .unwrap() 837 | .bind_index_buffer(indices) 838 | .unwrap() 839 | .bind_vertex_buffers(0, [vertices]) 840 | .unwrap() 841 | .set_viewport( 842 | 0, 843 | [Viewport { 844 | offset: [0.0, 0.0], 845 | extent: [ 846 | framebuffer_dimensions[0] as f32, 847 | framebuffer_dimensions[1] as f32, 848 | ], 849 | depth_range: 0.0..=1.0, 850 | }] 851 | .into_iter() 852 | .collect(), 853 | ) 854 | .unwrap() 855 | .push_constants(self.pipeline.layout().clone(), 0, push_constants) 856 | .unwrap(); 857 | } 858 | // Find and bind image, if different. 859 | if current_texture != Some(mesh.texture_id) { 860 | if self.texture_desc_sets.get(&mesh.texture_id).is_none() { 861 | eprintln!("This texture no longer exists {:?}", mesh.texture_id); 862 | continue; 863 | } 864 | current_texture = Some(mesh.texture_id); 865 | 866 | let desc_set = self.texture_desc_sets.get(&mesh.texture_id).unwrap(); 867 | 868 | builder 869 | .bind_descriptor_sets( 870 | PipelineBindPoint::Graphics, 871 | self.pipeline.layout().clone(), 872 | 0, 873 | desc_set.clone(), 874 | ) 875 | .unwrap(); 876 | }; 877 | // Calculate and set scissor, if different 878 | if current_rect != Some(*clip_rect) { 879 | current_rect = Some(*clip_rect); 880 | let new_scissor = 881 | self.get_rect_scissor(scale_factor, framebuffer_dimensions, *clip_rect); 882 | 883 | builder.set_scissor(0, [new_scissor].into_iter().collect()).unwrap(); 884 | } 885 | 886 | // All set up to draw! 887 | unsafe { 888 | builder 889 | .draw_indexed( 890 | mesh.indices.len() as u32, 891 | 1, 892 | index_cursor, 893 | vertex_cursor as i32, 894 | 0, 895 | ) 896 | .unwrap(); 897 | } 898 | 899 | // Consume this mesh for next iteration 900 | index_cursor += mesh.indices.len() as u32; 901 | vertex_cursor += mesh.vertices.len() as u32; 902 | } 903 | Primitive::Callback(callback) => { 904 | if callback.rect.is_positive() { 905 | let Some(callback_fn) = callback.callback.downcast_ref::() 906 | else { 907 | println!( 908 | "Warning: Unsupported render callback. Expected \ 909 | egui_winit_vulkano::CallbackFn" 910 | ); 911 | continue; 912 | }; 913 | 914 | let rect_min_x = scale_factor * callback.rect.min.x; 915 | let rect_min_y = scale_factor * callback.rect.min.y; 916 | let rect_max_x = scale_factor * callback.rect.max.x; 917 | let rect_max_y = scale_factor * callback.rect.max.y; 918 | 919 | let rect_min_x = rect_min_x.round(); 920 | let rect_min_y = rect_min_y.round(); 921 | let rect_max_x = rect_max_x.round(); 922 | let rect_max_y = rect_max_y.round(); 923 | 924 | builder 925 | .set_viewport( 926 | 0, 927 | [Viewport { 928 | offset: [rect_min_x, rect_min_y], 929 | extent: [rect_max_x - rect_min_x, rect_max_y - rect_min_y], 930 | depth_range: 0.0..=1.0, 931 | }] 932 | .into_iter() 933 | .collect(), 934 | ) 935 | .unwrap() 936 | .set_scissor( 937 | 0, 938 | [self.get_rect_scissor( 939 | scale_factor, 940 | framebuffer_dimensions, 941 | *clip_rect, 942 | )] 943 | .into_iter() 944 | .collect(), 945 | ) 946 | .unwrap(); 947 | 948 | let info = egui::PaintCallbackInfo { 949 | viewport: callback.rect, 950 | clip_rect: *clip_rect, 951 | pixels_per_point: scale_factor, 952 | screen_size_px: framebuffer_dimensions, 953 | }; 954 | (callback_fn.f)(info, &mut CallbackContext { 955 | builder, 956 | resources: self.render_resources(), 957 | }); 958 | 959 | // The user could have done much here - rebind pipes, set views, bind things, etc. 960 | // Mark all state as lost so that next mesh rebinds everything to a known state. 961 | needs_full_rebind = true; 962 | current_rect = None; 963 | current_texture = None; 964 | } 965 | } 966 | } 967 | } 968 | } 969 | 970 | pub fn render_resources(&self) -> RenderResources { 971 | RenderResources { 972 | queue: self.queue(), 973 | subpass: self.subpass.clone(), 974 | memory_allocator: self.allocators.memory.clone(), 975 | descriptor_set_allocator: &self.allocators.descriptor_set, 976 | command_buffer_allocator: &self.allocators.command_buffer, 977 | } 978 | } 979 | 980 | pub fn queue(&self) -> Arc { 981 | self.gfx_queue.clone() 982 | } 983 | 984 | pub fn allocators(&self) -> &Allocators { 985 | &self.allocators 986 | } 987 | } 988 | 989 | /// A set of objects used to perform custom rendering in a `PaintCallback`. 990 | /// 991 | /// It includes [`RenderResources`] for constructing a subpass pipeline and a secondary 992 | /// command buffer for pushing render commands onto it. 993 | /// 994 | /// # Example 995 | /// 996 | /// See the `triangle` demo source for a detailed usage example. 997 | pub struct CallbackContext<'a> { 998 | pub builder: &'a mut AutoCommandBufferBuilder, 999 | pub resources: RenderResources<'a>, 1000 | } 1001 | 1002 | /// A set of resources used to construct the render pipeline. These can be reused 1003 | /// to create additional pipelines and buffers to be rendered in a `PaintCallback`. 1004 | /// 1005 | /// # Example 1006 | /// 1007 | /// See the `triangle` demo source for a detailed usage example. 1008 | #[derive(Clone)] 1009 | pub struct RenderResources<'a> { 1010 | pub memory_allocator: Arc, 1011 | pub descriptor_set_allocator: &'a StandardDescriptorSetAllocator, 1012 | pub command_buffer_allocator: &'a StandardCommandBufferAllocator, 1013 | pub queue: Arc, 1014 | pub subpass: Subpass, 1015 | } 1016 | 1017 | pub type CallbackFnDef = dyn Fn(PaintCallbackInfo, &mut CallbackContext) + Sync + Send; 1018 | 1019 | /// A callback function that can be used to compose an [`epaint::PaintCallback`] for 1020 | /// custom rendering with [`vulkano`]. 1021 | /// 1022 | /// The callback is passed an [`egui::PaintCallbackInfo`] and a [`CallbackContext`] which 1023 | /// can be used to construct Vulkano graphics pipelines and buffers. 1024 | /// 1025 | /// # Example 1026 | /// 1027 | /// See the `triangle` demo source for a detailed usage example. 1028 | pub struct CallbackFn { 1029 | pub(crate) f: Box, 1030 | } 1031 | 1032 | impl CallbackFn { 1033 | pub fn new( 1034 | callback: F, 1035 | ) -> Self { 1036 | let f = Box::new(callback); 1037 | CallbackFn { f } 1038 | } 1039 | } 1040 | 1041 | mod vs { 1042 | vulkano_shaders::shader! { 1043 | ty: "vertex", 1044 | src: " 1045 | #version 450 1046 | 1047 | layout(location = 0) in vec2 position; 1048 | layout(location = 1) in vec2 tex_coords; 1049 | layout(location = 2) in vec4 color; 1050 | 1051 | layout(location = 0) out vec4 v_color; 1052 | layout(location = 1) out vec2 v_tex_coords; 1053 | 1054 | layout(push_constant) uniform PushConstants { 1055 | vec2 screen_size; 1056 | int output_in_linear_colorspace; 1057 | } push_constants; 1058 | 1059 | void main() { 1060 | gl_Position = vec4( 1061 | 2.0 * position.x / push_constants.screen_size.x - 1.0, 1062 | 2.0 * position.y / push_constants.screen_size.y - 1.0, 1063 | 0.0, 1.0 1064 | ); 1065 | v_color = color; 1066 | v_tex_coords = tex_coords; 1067 | }" 1068 | } 1069 | } 1070 | 1071 | // Similar to https://github.com/ArjunNair/egui_sdl2_gl/blob/main/src/painter.rs 1072 | mod fs { 1073 | vulkano_shaders::shader! { 1074 | ty: "fragment", 1075 | src: " 1076 | #version 450 1077 | 1078 | layout(location = 0) in vec4 v_color; 1079 | layout(location = 1) in vec2 v_tex_coords; 1080 | 1081 | layout(location = 0) out vec4 f_color; 1082 | 1083 | layout(binding = 0, set = 0) uniform sampler2D font_texture; 1084 | 1085 | layout(push_constant) uniform PushConstants { 1086 | vec2 screen_size; 1087 | int output_in_linear_colorspace; 1088 | } push_constants; 1089 | 1090 | // 0-1 sRGB from 0-1 linear 1091 | vec3 srgb_from_linear(vec3 linear) { 1092 | bvec3 cutoff = lessThan(linear, vec3(0.0031308)); 1093 | vec3 lower = linear * vec3(12.92); 1094 | vec3 higher = vec3(1.055) * pow(linear, vec3(1./2.4)) - vec3(0.055); 1095 | return mix(higher, lower, vec3(cutoff)); 1096 | } 1097 | 1098 | // 0-1 sRGBA from 0-1 linear 1099 | vec4 srgba_from_linear(vec4 linear) { 1100 | return vec4(srgb_from_linear(linear.rgb), linear.a); 1101 | } 1102 | 1103 | // 0-1 linear from 0-1 sRGB 1104 | vec3 linear_from_srgb(vec3 srgb) { 1105 | bvec3 cutoff = lessThan(srgb, vec3(0.04045)); 1106 | vec3 lower = srgb / vec3(12.92); 1107 | vec3 higher = pow((srgb + vec3(0.055) / vec3(1.055)), vec3(2.4)); 1108 | return mix(higher, lower, vec3(cutoff)); 1109 | } 1110 | 1111 | // 0-1 linear from 0-1 sRGB 1112 | vec4 linear_from_srgba(vec4 srgb) { 1113 | return vec4(linear_from_srgb(srgb.rgb), srgb.a); 1114 | } 1115 | 1116 | void main() { 1117 | // ALL calculations should be done in gamma space, this includes texture * color and blending 1118 | vec4 texture_color = srgba_from_linear(texture(font_texture, v_tex_coords)); 1119 | vec4 color = v_color * texture_color; 1120 | 1121 | // If output_in_linear_colorspace is true, we are rendering into an sRGB image, for which we'll convert to linear color space. 1122 | // **This will break blending** as it will be performed in linear color space instead of sRGB like egui expects. 1123 | if (push_constants.output_in_linear_colorspace == 1) { 1124 | color = linear_from_srgba(color); 1125 | } 1126 | f_color = color; 1127 | }" 1128 | } 1129 | } 1130 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Okko Hakola 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | use std::sync::Arc; 11 | 12 | #[cfg(feature = "image")] 13 | use image::RgbaImage; 14 | use vulkano::{ 15 | buffer::{AllocateBufferError, Buffer, BufferCreateInfo, BufferUsage}, 16 | command_buffer::{ 17 | allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo}, 18 | AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferToImageInfo, 19 | PrimaryCommandBufferAbstract, 20 | }, 21 | descriptor_set::allocator::StandardDescriptorSetAllocator, 22 | device::{Device, Queue}, 23 | image::{view::ImageView, AllocateImageError, Image, ImageCreateInfo, ImageType, ImageUsage}, 24 | memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, 25 | Validated, ValidationError, VulkanError, 26 | }; 27 | 28 | #[derive(Debug)] 29 | pub enum ImageCreationError { 30 | Vulkan(Validated), 31 | AllocateImage(Validated), 32 | AllocateBuffer(Validated), 33 | Validation(Box), 34 | } 35 | 36 | pub fn immutable_texture_from_bytes( 37 | allocators: &Allocators, 38 | queue: Arc, 39 | byte_data: &[u8], 40 | dimensions: [u32; 2], 41 | format: vulkano::format::Format, 42 | ) -> Result, ImageCreationError> { 43 | let mut cbb = AutoCommandBufferBuilder::primary( 44 | allocators.command_buffer.clone(), 45 | queue.queue_family_index(), 46 | CommandBufferUsage::OneTimeSubmit, 47 | ) 48 | .map_err(ImageCreationError::Vulkan)?; 49 | 50 | let texture_data_buffer = Buffer::from_iter( 51 | allocators.memory.clone(), 52 | BufferCreateInfo { usage: BufferUsage::TRANSFER_SRC, ..Default::default() }, 53 | AllocationCreateInfo { 54 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 55 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 56 | ..Default::default() 57 | }, 58 | byte_data.iter().cloned(), 59 | ) 60 | .map_err(ImageCreationError::AllocateBuffer)?; 61 | 62 | let texture = Image::new( 63 | allocators.memory.clone(), 64 | ImageCreateInfo { 65 | image_type: ImageType::Dim2d, 66 | format, 67 | extent: [dimensions[0], dimensions[1], 1], 68 | usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED, 69 | ..Default::default() 70 | }, 71 | AllocationCreateInfo::default(), 72 | ) 73 | .map_err(ImageCreationError::AllocateImage)?; 74 | 75 | cbb.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image( 76 | texture_data_buffer, 77 | texture.clone(), 78 | )) 79 | .map_err(ImageCreationError::Validation)?; 80 | 81 | let _fut = cbb.build().unwrap().execute(queue).unwrap(); 82 | 83 | Ok(ImageView::new_default(texture).unwrap()) 84 | } 85 | 86 | #[cfg(feature = "image")] 87 | pub fn immutable_texture_from_file( 88 | allocators: &Allocators, 89 | queue: Arc, 90 | file_bytes: &[u8], 91 | format: vulkano::format::Format, 92 | ) -> Result, ImageCreationError> { 93 | use image::GenericImageView; 94 | 95 | let img = image::load_from_memory(file_bytes).expect("Failed to load image from bytes"); 96 | let rgba = if let Some(rgba) = img.as_rgba8() { 97 | rgba.to_owned().to_vec() 98 | } else { 99 | // Convert rgb to rgba 100 | let rgb = img.as_rgb8().unwrap().to_owned(); 101 | let mut raw_data = vec![]; 102 | for val in rgb.chunks(3) { 103 | raw_data.push(val[0]); 104 | raw_data.push(val[1]); 105 | raw_data.push(val[2]); 106 | raw_data.push(255); 107 | } 108 | let new_rgba = RgbaImage::from_raw(rgb.width(), rgb.height(), raw_data).unwrap(); 109 | new_rgba.to_vec() 110 | }; 111 | let dimensions = img.dimensions(); 112 | immutable_texture_from_bytes(allocators, queue, &rgba, [dimensions.0, dimensions.1], format) 113 | } 114 | 115 | pub struct Allocators { 116 | pub memory: Arc, 117 | pub descriptor_set: Arc, 118 | pub command_buffer: Arc, 119 | } 120 | 121 | impl Allocators { 122 | pub fn new_default(device: &Arc) -> Self { 123 | Self { 124 | memory: Arc::new(StandardMemoryAllocator::new_default(device.clone())), 125 | descriptor_set: StandardDescriptorSetAllocator::new(device.clone(), Default::default()) 126 | .into(), 127 | command_buffer: StandardCommandBufferAllocator::new( 128 | device.clone(), 129 | StandardCommandBufferAllocatorCreateInfo { 130 | secondary_buffer_count: 32, 131 | ..Default::default() 132 | }, 133 | ) 134 | .into(), 135 | } 136 | } 137 | } 138 | --------------------------------------------------------------------------------