├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── other.md ├── pull_request_template.md └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE.md ├── README.md ├── deny.toml ├── doc └── img │ └── screenshot.png ├── examples ├── cube.rs ├── custom-texture.rs └── hello-world.rs ├── release.toml ├── renovate.json ├── resources ├── checker.png └── cube.wgsl └── src ├── imgui.wgsl └── lib.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: imgui-wgpu-rs malfunctioning? Please provide a detailed bug report. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | 10 | 11 | ## Description 12 | 13 | 14 | ## Repro steps 15 | 16 | 17 | ## Extra Materials 18 | 19 | 20 | ## System Information 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a way imgui-wgpu-rs could be better. 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Description 10 | 11 | 12 | ## Proposed Solution 13 | 14 | 15 | ## Alternatives 16 | 17 | 18 | ## Additional context 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: Strange things you want to tell us 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | --- 8 | 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Checklist 4 | 5 | - [ ] `cargo clippy` reports no issues 6 | - [ ] `cargo doc` reports no issues 7 | - [ ] [`cargo deny`](https://github.com/EmbarkStudios/cargo-deny/) issues have been fixed or added to `deny.toml` 8 | - [ ] human-readable change descriptions added to the changelog under the "Unreleased" heading. 9 | - [ ] If the change does not affect the user (or is a process change), preface the change with "Internal:" 10 | - [ ] Add credit to yourself for each change: `Added new functionality @githubname`. 11 | 12 | ## Description 13 | 14 | ## Related Issues 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | timeout-minutes: 10 10 | 11 | strategy: 12 | matrix: 13 | include: 14 | # native stable 15 | - name: "stable linux" 16 | os: "ubuntu-latest" 17 | target: "x86_64-unknown-linux-gnu" 18 | rust_version: "stable" 19 | - name: "stable mac" 20 | os: "macos-latest" 21 | target: "x86_64-apple-darwin" 22 | rust_version: "stable" 23 | - name: "stable windows" 24 | os: "windows-latest" 25 | target: "x86_64-pc-windows-msvc" 26 | rust_version: "stable" 27 | fail-fast: false 28 | runs-on: ${{ matrix.os }} 29 | name: ${{ matrix.name }} 30 | 31 | steps: 32 | - name: checkout repo 33 | uses: actions/checkout@v4 34 | 35 | - uses: Swatinem/rust-cache@v2 36 | 37 | - name: install rust 38 | uses: actions-rs/toolchain@v1 39 | with: 40 | toolchain: ${{ matrix.rust_version }} 41 | target: ${{ matrix.target }} 42 | profile: minimal 43 | components: clippy 44 | default: true 45 | 46 | - name: check 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: clippy 50 | args: --target ${{ matrix.target }} --examples --tests -- -D warnings 51 | 52 | - name: build 53 | uses: actions-rs/cargo@v1 54 | with: 55 | command: build 56 | args: --examples --tests --target ${{ matrix.target }} 57 | 58 | - name: doc 59 | uses: actions-rs/cargo@v1 60 | env: 61 | RUSTDOCFLAGS: -D warnings 62 | with: 63 | command: doc 64 | args: --no-deps --target ${{ matrix.target }} 65 | 66 | cargo-fmt: 67 | runs-on: ubuntu-latest 68 | steps: 69 | - name: checkout repo 70 | uses: actions/checkout@v4 71 | 72 | - name: install rust 73 | uses: actions-rs/toolchain@v1 74 | with: 75 | toolchain: nightly 76 | profile: minimal 77 | components: rustfmt 78 | default: true 79 | 80 | - name: check format 81 | uses: actions-rs/cargo@v1 82 | with: 83 | command: fmt 84 | args: -- --check 85 | 86 | cargo-deny: 87 | runs-on: ubuntu-latest 88 | steps: 89 | - name: checkout repo 90 | uses: actions/checkout@v4 91 | 92 | - name: check denies 93 | uses: EmbarkStudios/cargo-deny-action@v2 94 | with: 95 | log-level: warn 96 | command: check 97 | arguments: --all-features 98 | 99 | publish: 100 | runs-on: ubuntu-latest 101 | 102 | needs: ["build", "cargo-fmt", "cargo-deny"] 103 | if: ${{ startsWith(github.ref, 'refs/tags/v') }} 104 | 105 | steps: 106 | - name: checkout repo 107 | uses: actions/checkout@v4 108 | with: 109 | fetch-depth: 0 110 | 111 | - name: install rust 112 | uses: actions-rs/toolchain@v1 113 | with: 114 | toolchain: stable 115 | profile: minimal 116 | default: true 117 | 118 | - name: install cargo-release 119 | uses: taiki-e/install-action@v2 120 | with: 121 | tool: cargo-release 122 | 123 | - name: release to crates.io 124 | run: | 125 | git config user.name "releasebot" 126 | git config user.email "actions@users.noreply.github.com" 127 | git checkout master 128 | 129 | cargo login ${{ secrets.CRATES_TOKEN }} 130 | cargo release --no-confirm --execute $( echo '${{ github.ref }}' | sed 's?refs/tags/v??' ) 131 | 132 | - name: generate release notes 133 | id: notes 134 | run: | 135 | NOTES=$(python -c 'import re; print(re.search("## v\\d+.\\d+.\\d+\n\nReleased \\d+-\\d+-\\d+\n\n((?:[\n]|.)*?)(?=## [vD])", open("CHANGELOG.md", "r").read()).group(1).strip().replace("%", "%25").replace("\n", "%0A"))') 136 | echo "::set-output name=notes::$NOTES" 137 | 138 | - name: release to github 139 | uses: actions/create-release@v1 140 | env: 141 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 142 | with: 143 | tag_name: ${{ github.ref }} 144 | release_name: ${{ github.ref }} 145 | body: ${{ steps.notes.outputs.notes }} 146 | draft: false 147 | prerelease: false 148 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to cargo's version of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | Per Keep a Changelog there are 6 main categories of changes: 9 | - Added 10 | - Changed 11 | - Deprecated 12 | - Removed 13 | - Fixed 14 | - Security 15 | 16 | #### Table of Contents 17 | 18 | - [Unreleased](#unreleased) 19 | - [v0.24.0](#v0240) 20 | - [v0.22.0](#v0220) 21 | - [v0.21.0](#v0210) 22 | - [v0.20.0](#v0200) 23 | - [v0.19.0](#v0190) 24 | - [v0.18.0](#v0180) 25 | - [v0.17.2](#v0172) 26 | - [v0.17.1](#v0171) 27 | - [v0.17.0](#v0170) 28 | - [v0.16.0](#v0160) 29 | - [v0.15.1](#v0151) 30 | - [v0.15.0](#v0150) 31 | - [v0.14.0](#v0140) 32 | - [v0.13.1](#v0131) 33 | - [v0.13.0](#v0130) 34 | - [v0.12.0](#v0120) 35 | - [Diffs](#diffs) 36 | 37 | ## Unreleased 38 | 39 | - Internal: Fixed Scissor-Rect to not span across Framebuffersize, by limiting to framebuffer width. @PixelboysTM 40 | - Bump wgpu version to 0.19. @mkrasnitski and @calcoph 41 | - Bump wgpu version to 22.1. @aftix 42 | - Bump wgpu version to 23.0. @SupernaviX 43 | - Internal: Update cargo-deny config to handle breaking changes. @SupernaviX 44 | 45 | ## v0.24.0 46 | 47 | Released 2023-08-02 48 | 49 | - Bump wgpu version to 0.17. @cwfitzgerald 50 | 51 | ## v0.23.0 52 | 53 | - Bump imgui version to 0.11.0. @benmkw 54 | - Fix issue with scissors due to wrong resizing logic. @dcvz 55 | - Bump wgpu version to 0.16. @Nelarius 56 | 57 | ## v0.22.0 58 | 59 | Released 2023-02-10 60 | - Change `BindGroup` inside `Texture::from_raw_parts` to `Option` to allow bind group being created by `imgui-wgpu-rs` @BeastLe9enD 61 | - Make `Texture::from_raw_parts` take `Arc` instead of `T` to avoid being forced to move into the texture @BeastLe9enD 62 | - Moved from Rust Edition 2018 -> 2021 @Snowiiii 63 | - Updated `imgui` to 0.10 and `wgpu` to 0.15. @parasyte 64 | 65 | ## v0.21.0 66 | 67 | Released 2022-12-17 68 | - Bump imgui version to 0.9.0. Fix examples to match. @aholtzma-am 69 | 70 | ## v0.20.0 71 | 72 | Released 2022-07-11 73 | 74 | ### Updated 75 | - updated `wgpu` to 0.13 @Davidster 76 | - Internal: Use Fifo present mode in examples @Davidster 77 | 78 | ### Fixed 79 | - Fix issues with resizing due to the framebuffer size not being updated. @druks-232cc 80 | 81 | ## v0.19.0 82 | 83 | Released 2021-12-30 84 | 85 | ### Changed 86 | - Split up render into two internal functions, `prepare` and `split_render`. 87 | - Add `SamplerDesc` to TextureConfig 88 | 89 | ### Updated 90 | - updated wgpu dependency to `0.12` 91 | 92 | ### Removed 93 | - unreleased `simple-api` and moved to https://github.com/benmkw/imgui-wgpu-simple-api 94 | 95 | ## v0.18.0 96 | 97 | Released 2021-10-08 98 | 99 | ## v0.17.2 100 | 101 | Released 2021-10-08 102 | 103 | #### Updated 104 | - updated wgpu dependency to `>=0.10,<0.12` 105 | 106 | ## v0.17.1 107 | 108 | Released 2021-09-22 109 | 110 | #### Updated 111 | - updated imgui dependency to `>=0.1,<0.9` 112 | 113 | #### Removed 114 | - unstable simple-api is now it's own, unpublished, crate. 115 | 116 | ## v0.17.0 117 | 118 | Released 2021-09-04 119 | 120 | #### Changed 121 | - Internal: translate shaders from SPIR-V to WGSL 122 | 123 | #### Updated 124 | - updated `wgpu` to 0.10 125 | 126 | #### Fixed 127 | - Internal: fix all warnings from static analysis (clippy). 128 | - Internal: Do not render draw commands that fall outside the framebuffer 129 | - Internal: Avoid wgpu logic error by not rendering empty clip rects 130 | 131 | ## v0.16.0 132 | 133 | Released 2021-07-14 134 | 135 | #### Added 136 | - Internal: Vastly improved CI and release process. 137 | - Internal: PR and Issue Templates 138 | 139 | #### Changed 140 | - Examples: Use `env_logger` instead of `wgpu-subscriber` 141 | - Examples: Use `pollster` as block_on provider instead of `futures` 142 | 143 | #### Fixed 144 | - Rendering to multi-sampled images no longer errors. 145 | - Examples: Simple API examples now properly depend on that feature existing. 146 | 147 | #### Updated 148 | - updated `wgpu` to 0.9 149 | 150 | ## v0.15.1 151 | 152 | Released 2021-05-08 153 | 154 | #### Fixed 155 | - removed hack due to wgpu bug 156 | 157 | #### Updated 158 | - updated `wgpu` to 0.8.1 159 | 160 | ## v0.15.0 161 | 162 | Released 2021-05-08 163 | 164 | #### Updated 165 | - updated `wgpu` to 0.8 166 | 167 | ## v0.14.0 168 | 169 | Released 2021-02-12 170 | 171 | #### Updated 172 | - updated `imgui` to 0.7 173 | 174 | ## v0.13.1 175 | 176 | Released 2021-02-01 177 | 178 | #### Fixed 179 | - Readme 180 | 181 | ## v0.13.0 182 | 183 | Released 2021-02-01 184 | 185 | #### Added 186 | - Add experimental simple api behind feature `simple_api_unstable` 187 | - Implemented `std::error::Error` for `RendererError` 188 | 189 | #### Updated 190 | - updated to `wgpu` 0.7 191 | - support `winit` 0.24 as well as 0.23 192 | 193 | ## v0.12.0 194 | 195 | Released 2020-11-21 196 | 197 | #### Added 198 | - A changelog! 199 | - Shaders are now SRGB aware. Choose `RendererConfig::new()` to get shaders outputting in linear color 200 | and `RendererConfig::new_srgb()` for shaders outputting SRGB. 201 | 202 | #### Updated 203 | - `imgui` to `0.6`. 204 | - `winit` to `0.23` 205 | 206 | #### Removed 207 | - GLSL shaders and `glsl-to-spirv`. If you want a custom shader, provide custom spirv to `RendererConfig::with_shaders()`, however you must generate it. 208 | 209 | ## Diffs 210 | 211 | - [Unreleased](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.24.0...HEAD) 212 | - [v0.24.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.22.0...v0.24.0) 213 | - [v0.22.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.21.0...v0.22.0) 214 | - [v0.21.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.20.0...v0.21.0) 215 | - [v0.20.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.19.0...v0.20.0) 216 | - [v0.19.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.18.0...v0.19.0) 217 | - [v0.18.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.17.2...v0.18.0) 218 | - [v0.17.2](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.17.1...v0.17.2) 219 | - [v0.17.1](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.17.0...v0.17.1) 220 | - [v0.17.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.16.0...v0.17.0) 221 | - [v0.16.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.15.1...v0.16.0) 222 | - [v0.15.1](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.15.0...v0.15.1) 223 | - [v0.15.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.14.0...v0.15.0) 224 | - [v0.14.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.13.1...v0.14.0) 225 | - [v0.13.1](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.13.0...v0.13.1) 226 | - [v0.13.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.12.0...v0.13.0) 227 | - [v0.12.0](https://github.com/Yatekii/imgui-wgpu-rs/compare/v0.11.0...v0.12.0) 228 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "imgui-wgpu" 3 | version = "0.25.0" 4 | authors = [ 5 | "Noah Hüsser ", 6 | "Connor Fitzgerald ", 7 | "Steven Wittens ", 8 | ] 9 | edition = "2021" 10 | description = "A wgpu render backend for imgui-rs." 11 | documentation = "https://docs.rs/imgui-wgpu/" 12 | homepage = "https://github.com/Yatekii/imgui-wgpu-rs" 13 | repository = "https://github.com/Yatekii/imgui-wgpu-rs" 14 | readme = "README.md" 15 | categories = ["gui", "graphics", "rendering", "rendering::graphics-api"] 16 | keywords = ["gui", "graphics", "wgpu", "imgui"] 17 | license = "MIT OR Apache-2.0" 18 | 19 | exclude = [".gitignore", ".github", "resources"] 20 | 21 | [[package.metadata.release.pre-release-replacements]] 22 | file = "CHANGELOG.md" 23 | search = "\\[Unreleased\\]\\(#unreleased\\)" 24 | replace = "[Unreleased](#unreleased)\n- [v{{version}}](#v{{version}})" 25 | [[package.metadata.release.pre-release-replacements]] 26 | file = "CHANGELOG.md" 27 | search = "\\[v([0-9]+)\\.([0-9]+)\\.([0-9]+)\\]\\(#v[0-9\\.]+\\)" 28 | replace = "[v$1.$2.$3](#v$1$2$3)" 29 | [[package.metadata.release.pre-release-replacements]] 30 | file = "CHANGELOG.md" 31 | search = "## Unreleased" 32 | replace = "## Unreleased\n\n## v{{version}}\n\nReleased {{date}}" 33 | [[package.metadata.release.pre-release-replacements]] 34 | file = "CHANGELOG.md" 35 | search = "\\[Unreleased\\]\\(https://github.com/Yatekii/imgui-wgpu-rs/compare/v([a-z0-9.-]+)\\.\\.\\.HEAD\\)" 36 | replace = "[Unreleased](https://github.com/Yatekii/imgui-wgpu-rs/compare/v{{version}}...HEAD)\n- [v{{version}}](https://github.com/Yatekii/imgui-wgpu-rs/compare/v$1...v{{version}})" 37 | min = 0 # allow first increment 38 | [[package.metadata.release.pre-release-replacements]] 39 | file = "CHANGELOG.md" 40 | search = "" 41 | replace = "- [Unreleased](https://github.com/Yatekii/imgui-wgpu-rs/compare/v{{version}}...HEAD)" 42 | min = 0 # allow non-first increment 43 | 44 | [dependencies] 45 | bytemuck = "1" 46 | imgui = "0.12" 47 | log = "0.4" 48 | smallvec = "1" 49 | wgpu = "25.0.0" 50 | 51 | [dev-dependencies] 52 | bytemuck = { version = "1.13", features = ["derive"] } 53 | cgmath = "0.18" 54 | env_logger = "0.11" 55 | image = { version = "0.25", default-features = false, features = ["png"] } 56 | imgui-winit-support = "0.13" 57 | pollster = "0.4" 58 | raw-window-handle = "0.6" 59 | winit = "0.30" 60 | 61 | [package.metadata.docs.rs] 62 | all-features = true 63 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Steven Wittens 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dear imgui wgpu-rs renderer 2 | 3 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/Yatekii/imgui-wgpu-rs/build.yml?branch=master) 4 | [![Documentation](https://docs.rs/imgui-wgpu/badge.svg)](https://docs.rs/imgui-wgpu) 5 | [![Crates.io](https://img.shields.io/crates/v/imgui-wgpu)](https://crates.io/crates/imgui-wgpu) 6 | ![License](https://img.shields.io/crates/l/imgui-wgpu) 7 | 8 | Draw dear imgui UIs as a wgpu render pass. Based on [imgui-gfx-renderer](https://github.com/Gekkio/imgui-rs/tree/master/imgui-gfx-renderer) from [imgui-rs](https://github.com/Gekkio/imgui-rs). 9 | 10 | ![screenshot](doc/img/screenshot.png) 11 | 12 | # Usage 13 | 14 | For usage, please have a look at the [example](examples/hello-world.rs). 15 | 16 | # Example 17 | 18 | Run the example with 19 | ``` 20 | cargo run --release --example hello-world 21 | ``` 22 | 23 | # Status 24 | 25 | Supports `wgpu` `0.17` and imgui `0.11`. `winit-0.27` is used with the examples. 26 | 27 | Contributions are very welcome. 28 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | allow = [ 3 | "Apache-2.0", 4 | "CC0-1.0", 5 | "ISC", 6 | "MIT", 7 | "Unicode-3.0", 8 | "Unlicense", 9 | "Zlib", 10 | ] 11 | 12 | [bans] 13 | multiple-versions = "deny" 14 | skip = [ 15 | { name = "bitflags", version = "1.3.2" }, 16 | { name = "thiserror", version = "1.0.69" }, 17 | { name = "thiserror-impl", version = "1.0.69" }, 18 | ] 19 | 20 | [sources] 21 | unknown-registry = "deny" 22 | unknown-git = "allow" 23 | 24 | [advisories] 25 | ignore = [ 26 | # paste is unmaintained, but is complete. 27 | "RUSTSEC-2024-0436", 28 | ] 29 | -------------------------------------------------------------------------------- /doc/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yatekii/imgui-wgpu-rs/e55979ed671e1dcf2293538a863581fe4f776e09/doc/img/screenshot.png -------------------------------------------------------------------------------- /examples/cube.rs: -------------------------------------------------------------------------------- 1 | use bytemuck::{Pod, Zeroable}; 2 | use imgui::*; 3 | use imgui_wgpu::{Renderer, RendererConfig, Texture, TextureConfig}; 4 | use imgui_winit_support::WinitPlatform; 5 | use pollster::block_on; 6 | use std::{sync::Arc, time::Instant}; 7 | use wgpu::{include_wgsl, util::DeviceExt, Extent3d}; 8 | use winit::{ 9 | application::ApplicationHandler, 10 | dpi::LogicalSize, 11 | event::{Event, WindowEvent}, 12 | event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, 13 | keyboard::{Key, NamedKey}, 14 | window::Window, 15 | }; 16 | 17 | const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( 18 | 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5, 1.0, 19 | ); 20 | 21 | // Example code modified from https://github.com/gfx-rs/wgpu-rs/tree/master/examples/cube 22 | #[repr(C)] 23 | #[derive(Clone, Copy, Pod, Zeroable)] 24 | struct Vertex { 25 | _pos: [f32; 4], 26 | _tex_coord: [f32; 2], 27 | } 28 | 29 | fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex { 30 | Vertex { 31 | _pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0], 32 | _tex_coord: [tc[0] as f32, tc[1] as f32], 33 | } 34 | } 35 | 36 | fn create_vertices() -> (Vec, Vec) { 37 | let vertex_data = [ 38 | // top (0, 0, 1) 39 | vertex([-1, -1, 1], [0, 0]), 40 | vertex([1, -1, 1], [1, 0]), 41 | vertex([1, 1, 1], [1, 1]), 42 | vertex([-1, 1, 1], [0, 1]), 43 | // bottom (0, 0, -1) 44 | vertex([-1, 1, -1], [1, 0]), 45 | vertex([1, 1, -1], [0, 0]), 46 | vertex([1, -1, -1], [0, 1]), 47 | vertex([-1, -1, -1], [1, 1]), 48 | // right (1, 0, 0) 49 | vertex([1, -1, -1], [0, 0]), 50 | vertex([1, 1, -1], [1, 0]), 51 | vertex([1, 1, 1], [1, 1]), 52 | vertex([1, -1, 1], [0, 1]), 53 | // left (-1, 0, 0) 54 | vertex([-1, -1, 1], [1, 0]), 55 | vertex([-1, 1, 1], [0, 0]), 56 | vertex([-1, 1, -1], [0, 1]), 57 | vertex([-1, -1, -1], [1, 1]), 58 | // front (0, 1, 0) 59 | vertex([1, 1, -1], [1, 0]), 60 | vertex([-1, 1, -1], [0, 0]), 61 | vertex([-1, 1, 1], [0, 1]), 62 | vertex([1, 1, 1], [1, 1]), 63 | // back (0, -1, 0) 64 | vertex([1, -1, 1], [0, 0]), 65 | vertex([-1, -1, 1], [1, 0]), 66 | vertex([-1, -1, -1], [1, 1]), 67 | vertex([1, -1, -1], [0, 1]), 68 | ]; 69 | 70 | let index_data: &[u16] = &[ 71 | 0, 1, 2, 2, 3, 0, // top 72 | 4, 5, 6, 6, 7, 4, // bottom 73 | 8, 9, 10, 10, 11, 8, // right 74 | 12, 13, 14, 14, 15, 12, // left 75 | 16, 17, 18, 18, 19, 16, // front 76 | 20, 21, 22, 22, 23, 20, // back 77 | ]; 78 | 79 | (vertex_data.to_vec(), index_data.to_vec()) 80 | } 81 | 82 | fn create_texels(size: usize) -> Vec { 83 | (0..size * size) 84 | .map(|id| { 85 | // get high five for recognizing this ;) 86 | let cx = 3.0 * (id % size) as f32 / (size - 1) as f32 - 2.0; 87 | let cy = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0; 88 | let (mut x, mut y, mut count) = (cx, cy, 0); 89 | while count < 0xFF && x * x + y * y < 4.0 { 90 | let old_x = x; 91 | x = x * x - y * y + cx; 92 | y = 2.0 * old_x * y + cy; 93 | count += 1; 94 | } 95 | count 96 | }) 97 | .collect() 98 | } 99 | 100 | struct Example { 101 | vertex_buf: wgpu::Buffer, 102 | index_buf: wgpu::Buffer, 103 | index_count: usize, 104 | bind_group: wgpu::BindGroup, 105 | uniform_buf: wgpu::Buffer, 106 | pipeline: wgpu::RenderPipeline, 107 | time: f32, 108 | } 109 | 110 | impl Example { 111 | fn generate_matrix(aspect_ratio: f32) -> cgmath::Matrix4 { 112 | let mx_projection = cgmath::perspective(cgmath::Deg(45f32), aspect_ratio, 1.0, 10.0); 113 | let mx_view = cgmath::Matrix4::look_at_rh( 114 | cgmath::Point3::new(1.5f32, -5.0, 3.0), 115 | cgmath::Point3::new(0f32, 0.0, 0.0), 116 | cgmath::Vector3::unit_z(), 117 | ); 118 | let mx_correction = OPENGL_TO_WGPU_MATRIX; 119 | mx_correction * mx_projection * mx_view 120 | } 121 | } 122 | 123 | impl Example { 124 | fn init( 125 | config: &wgpu::SurfaceConfiguration, 126 | device: &wgpu::Device, 127 | queue: &wgpu::Queue, 128 | ) -> Self { 129 | use std::mem; 130 | 131 | // Create the vertex and index buffers 132 | let vertex_size = mem::size_of::(); 133 | let (vertex_data, index_data) = create_vertices(); 134 | 135 | let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 136 | label: Some("Vertex Buffer"), 137 | contents: bytemuck::cast_slice(&vertex_data), 138 | usage: wgpu::BufferUsages::VERTEX, 139 | }); 140 | 141 | let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 142 | label: Some("Index Buffer"), 143 | contents: bytemuck::cast_slice(&index_data), 144 | usage: wgpu::BufferUsages::INDEX, 145 | }); 146 | 147 | // Create pipeline layout 148 | let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 149 | label: None, 150 | entries: &[ 151 | wgpu::BindGroupLayoutEntry { 152 | binding: 0, 153 | visibility: wgpu::ShaderStages::VERTEX, 154 | ty: wgpu::BindingType::Buffer { 155 | ty: wgpu::BufferBindingType::Uniform, 156 | has_dynamic_offset: false, 157 | min_binding_size: wgpu::BufferSize::new(64), 158 | }, 159 | count: None, 160 | }, 161 | wgpu::BindGroupLayoutEntry { 162 | binding: 1, 163 | visibility: wgpu::ShaderStages::FRAGMENT, 164 | ty: wgpu::BindingType::Texture { 165 | multisampled: false, 166 | sample_type: wgpu::TextureSampleType::Uint, 167 | view_dimension: wgpu::TextureViewDimension::D2, 168 | }, 169 | count: None, 170 | }, 171 | ], 172 | }); 173 | let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 174 | label: None, 175 | bind_group_layouts: &[&bind_group_layout], 176 | push_constant_ranges: &[], 177 | }); 178 | 179 | // Create the texture 180 | let size = 256u32; 181 | let texels = create_texels(size as usize); 182 | let texture_extent = wgpu::Extent3d { 183 | width: size, 184 | height: size, 185 | depth_or_array_layers: 1, 186 | }; 187 | let texture = device.create_texture(&wgpu::TextureDescriptor { 188 | label: None, 189 | size: texture_extent, 190 | mip_level_count: 1, 191 | sample_count: 1, 192 | dimension: wgpu::TextureDimension::D2, 193 | format: wgpu::TextureFormat::R8Uint, 194 | usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, 195 | view_formats: &[wgpu::TextureFormat::R8Uint], 196 | }); 197 | let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); 198 | queue.write_texture( 199 | texture.as_image_copy(), 200 | &texels, 201 | wgpu::TexelCopyBufferLayout { 202 | offset: 0, 203 | bytes_per_row: Some(size), 204 | rows_per_image: None, 205 | }, 206 | texture_extent, 207 | ); 208 | 209 | // Create other resources 210 | let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32); 211 | let mx_ref: &[f32; 16] = mx_total.as_ref(); 212 | let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 213 | label: Some("Uniform Buffer"), 214 | contents: bytemuck::cast_slice(mx_ref), 215 | usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, 216 | }); 217 | 218 | // Create bind group 219 | let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { 220 | layout: &bind_group_layout, 221 | entries: &[ 222 | wgpu::BindGroupEntry { 223 | binding: 0, 224 | resource: uniform_buf.as_entire_binding(), 225 | }, 226 | wgpu::BindGroupEntry { 227 | binding: 1, 228 | resource: wgpu::BindingResource::TextureView(&texture_view), 229 | }, 230 | ], 231 | label: None, 232 | }); 233 | 234 | let shader = device.create_shader_module(include_wgsl!("../resources/cube.wgsl")); 235 | 236 | let vertex_buffers = [wgpu::VertexBufferLayout { 237 | array_stride: vertex_size as wgpu::BufferAddress, 238 | step_mode: wgpu::VertexStepMode::Vertex, 239 | attributes: &[ 240 | wgpu::VertexAttribute { 241 | format: wgpu::VertexFormat::Float32x4, 242 | offset: 0, 243 | shader_location: 0, 244 | }, 245 | wgpu::VertexAttribute { 246 | format: wgpu::VertexFormat::Float32x2, 247 | offset: 4 * 4, 248 | shader_location: 1, 249 | }, 250 | ], 251 | }]; 252 | 253 | let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { 254 | label: None, 255 | layout: Some(&pipeline_layout), 256 | vertex: wgpu::VertexState { 257 | module: &shader, 258 | entry_point: Some("vs_main"), 259 | compilation_options: Default::default(), 260 | buffers: &vertex_buffers, 261 | }, 262 | fragment: Some(wgpu::FragmentState { 263 | module: &shader, 264 | entry_point: Some("fs_main"), 265 | compilation_options: Default::default(), 266 | targets: &[Some(config.format.into())], 267 | }), 268 | primitive: wgpu::PrimitiveState { 269 | cull_mode: Some(wgpu::Face::Back), 270 | ..Default::default() 271 | }, 272 | depth_stencil: None, 273 | multisample: wgpu::MultisampleState::default(), 274 | multiview: None, 275 | cache: None, 276 | }); 277 | 278 | // Done 279 | Example { 280 | vertex_buf, 281 | index_buf, 282 | index_count: index_data.len(), 283 | bind_group, 284 | uniform_buf, 285 | pipeline, 286 | time: 0.0, 287 | } 288 | } 289 | 290 | fn update(&mut self, delta_time: f32) { 291 | self.time += delta_time; 292 | } 293 | 294 | fn setup_camera(&mut self, queue: &wgpu::Queue, size: [f32; 2]) { 295 | let mx_total = Self::generate_matrix(size[0] / size[1]); 296 | let mx_ref: &[f32; 16] = mx_total.as_ref(); 297 | queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref)); 298 | } 299 | 300 | fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { 301 | let mut encoder = 302 | device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); 303 | { 304 | let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 305 | label: None, 306 | color_attachments: &[Some(wgpu::RenderPassColorAttachment { 307 | view, 308 | resolve_target: None, 309 | ops: wgpu::Operations { 310 | load: wgpu::LoadOp::Clear(wgpu::Color { 311 | r: 0.1, 312 | g: 0.2, 313 | b: 0.3, 314 | a: 1.0, 315 | }), 316 | store: wgpu::StoreOp::Store, 317 | }, 318 | })], 319 | depth_stencil_attachment: None, 320 | timestamp_writes: None, 321 | occlusion_query_set: None, 322 | }); 323 | rpass.push_debug_group("Prepare data for draw."); 324 | rpass.set_pipeline(&self.pipeline); 325 | rpass.set_bind_group(0, &self.bind_group, &[]); 326 | rpass.set_index_buffer(self.index_buf.slice(..), wgpu::IndexFormat::Uint16); 327 | rpass.set_vertex_buffer(0, self.vertex_buf.slice(..)); 328 | rpass.pop_debug_group(); 329 | rpass.insert_debug_marker("Draw!"); 330 | rpass.draw_indexed(0..self.index_count as u32, 0, 0..1); 331 | } 332 | 333 | queue.submit(Some(encoder.finish())); 334 | } 335 | } 336 | 337 | struct ImguiState { 338 | context: imgui::Context, 339 | platform: WinitPlatform, 340 | renderer: Renderer, 341 | example: Example, 342 | example_size: [f32; 2], 343 | example_texture_id: TextureId, 344 | last_frame: Instant, 345 | last_cursor: Option, 346 | } 347 | 348 | struct AppWindow { 349 | device: wgpu::Device, 350 | queue: wgpu::Queue, 351 | window: Arc, 352 | surface_desc: wgpu::SurfaceConfiguration, 353 | surface: wgpu::Surface<'static>, 354 | hidpi_factor: f64, 355 | imgui: Option, 356 | } 357 | 358 | #[derive(Default)] 359 | struct App { 360 | window: Option, 361 | } 362 | 363 | impl AppWindow { 364 | fn setup_gpu(event_loop: &ActiveEventLoop) -> Self { 365 | let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { 366 | backends: wgpu::Backends::PRIMARY, 367 | ..Default::default() 368 | }); 369 | 370 | let window = { 371 | let version = env!("CARGO_PKG_VERSION"); 372 | 373 | let size = LogicalSize::new(1280.0, 720.0); 374 | 375 | let attributes = Window::default_attributes() 376 | .with_inner_size(size) 377 | .with_title(format!("imgui-wgpu {version}")); 378 | Arc::new(event_loop.create_window(attributes).unwrap()) 379 | }; 380 | 381 | let size = window.inner_size(); 382 | let hidpi_factor = window.scale_factor(); 383 | let surface = instance.create_surface(window.clone()).unwrap(); 384 | 385 | let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { 386 | power_preference: wgpu::PowerPreference::HighPerformance, 387 | compatible_surface: Some(&surface), 388 | force_fallback_adapter: false, 389 | })) 390 | .unwrap(); 391 | 392 | let (device, queue) = 393 | block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).unwrap(); 394 | 395 | // Set up swap chain 396 | let surface_desc = wgpu::SurfaceConfiguration { 397 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT, 398 | format: wgpu::TextureFormat::Bgra8UnormSrgb, 399 | width: size.width, 400 | height: size.height, 401 | present_mode: wgpu::PresentMode::Fifo, 402 | desired_maximum_frame_latency: 2, 403 | alpha_mode: wgpu::CompositeAlphaMode::Auto, 404 | view_formats: vec![wgpu::TextureFormat::Bgra8Unorm], 405 | }; 406 | 407 | surface.configure(&device, &surface_desc); 408 | 409 | let imgui = None; 410 | Self { 411 | device, 412 | queue, 413 | window, 414 | surface_desc, 415 | surface, 416 | hidpi_factor, 417 | imgui, 418 | } 419 | } 420 | 421 | fn setup_imgui(&mut self) { 422 | let mut context = imgui::Context::create(); 423 | let mut platform = imgui_winit_support::WinitPlatform::new(&mut context); 424 | platform.attach_window( 425 | context.io_mut(), 426 | &self.window, 427 | imgui_winit_support::HiDpiMode::Default, 428 | ); 429 | context.set_ini_filename(None); 430 | 431 | let font_size = (13.0 * self.hidpi_factor) as f32; 432 | context.io_mut().font_global_scale = (1.0 / self.hidpi_factor) as f32; 433 | 434 | context.fonts().add_font(&[FontSource::DefaultFontData { 435 | config: Some(imgui::FontConfig { 436 | oversample_h: 1, 437 | pixel_snap_h: true, 438 | size_pixels: font_size, 439 | ..Default::default() 440 | }), 441 | }]); 442 | 443 | // 444 | // Set up dear imgui wgpu renderer 445 | // 446 | // let clear_color = wgpu::Color { 447 | // r: 0.1, 448 | // g: 0.2, 449 | // b: 0.3, 450 | // a: 1.0, 451 | // }; 452 | 453 | let renderer_config = RendererConfig { 454 | texture_format: self.surface_desc.format, 455 | ..Default::default() 456 | }; 457 | 458 | let mut renderer = Renderer::new(&mut context, &self.device, &self.queue, renderer_config); 459 | 460 | let last_frame = Instant::now(); 461 | let last_cursor = None; 462 | let example_size: [f32; 2] = [640.0, 480.0]; 463 | let example = Example::init(&self.surface_desc, &self.device, &self.queue); 464 | 465 | // Stores a texture for displaying with imgui::Image(), 466 | // also as a texture view for rendering into it 467 | let texture_config = TextureConfig { 468 | size: wgpu::Extent3d { 469 | width: example_size[0] as u32, 470 | height: example_size[1] as u32, 471 | ..Default::default() 472 | }, 473 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, 474 | ..Default::default() 475 | }; 476 | 477 | let texture = Texture::new(&self.device, &renderer, texture_config); 478 | let example_texture_id = renderer.textures.insert(texture); 479 | 480 | self.imgui = Some(ImguiState { 481 | context, 482 | platform, 483 | renderer, 484 | example, 485 | example_size, 486 | example_texture_id, 487 | last_frame, 488 | last_cursor, 489 | }) 490 | } 491 | 492 | fn new(event_loop: &ActiveEventLoop) -> Self { 493 | let mut window = Self::setup_gpu(event_loop); 494 | window.setup_imgui(); 495 | window 496 | } 497 | } 498 | 499 | impl ApplicationHandler for App { 500 | fn resumed(&mut self, event_loop: &ActiveEventLoop) { 501 | self.window = Some(AppWindow::new(event_loop)); 502 | } 503 | 504 | fn window_event( 505 | &mut self, 506 | event_loop: &ActiveEventLoop, 507 | window_id: winit::window::WindowId, 508 | event: WindowEvent, 509 | ) { 510 | let window = self.window.as_mut().unwrap(); 511 | let imgui = window.imgui.as_mut().unwrap(); 512 | 513 | match &event { 514 | WindowEvent::Resized(size) => { 515 | window.surface_desc = wgpu::SurfaceConfiguration { 516 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT, 517 | format: wgpu::TextureFormat::Bgra8UnormSrgb, 518 | width: size.width, 519 | height: size.height, 520 | present_mode: wgpu::PresentMode::Fifo, 521 | desired_maximum_frame_latency: 2, 522 | alpha_mode: wgpu::CompositeAlphaMode::Auto, 523 | view_formats: vec![wgpu::TextureFormat::Bgra8Unorm], 524 | }; 525 | 526 | window 527 | .surface 528 | .configure(&window.device, &window.surface_desc); 529 | } 530 | WindowEvent::CloseRequested => event_loop.exit(), 531 | WindowEvent::KeyboardInput { event, .. } => { 532 | if let Key::Named(NamedKey::Escape) = event.logical_key { 533 | if event.state.is_pressed() { 534 | event_loop.exit(); 535 | } 536 | } 537 | } 538 | WindowEvent::RedrawRequested => { 539 | let now = Instant::now(); 540 | imgui 541 | .context 542 | .io_mut() 543 | .update_delta_time(now - imgui.last_frame); 544 | imgui.last_frame = now; 545 | 546 | let frame = match window.surface.get_current_texture() { 547 | Ok(frame) => frame, 548 | Err(e) => { 549 | eprintln!("dropped frame: {e:?}"); 550 | return; 551 | } 552 | }; 553 | imgui 554 | .platform 555 | .prepare_frame(imgui.context.io_mut(), &window.window) 556 | .expect("Failed to prepare frame"); 557 | let ui = imgui.context.frame(); 558 | 559 | let view = frame 560 | .texture 561 | .create_view(&wgpu::TextureViewDescriptor::default()); 562 | 563 | // Render example normally at background 564 | imgui.example.update(ui.io().delta_time); 565 | imgui 566 | .example 567 | .setup_camera(&window.queue, ui.io().display_size); 568 | imgui.example.render(&view, &window.device, &window.queue); 569 | 570 | // Store the new size of Image() or None to indicate that the window is collapsed. 571 | let mut new_example_size: Option<[f32; 2]> = None; 572 | 573 | ui.window("Cube") 574 | .size([512.0, 512.0], Condition::FirstUseEver) 575 | .build(|| { 576 | new_example_size = Some(ui.content_region_avail()); 577 | imgui::Image::new(imgui.example_texture_id, new_example_size.unwrap()) 578 | .build(ui); 579 | }); 580 | 581 | if let Some(size) = new_example_size { 582 | // Resize render target, which is optional 583 | if size != imgui.example_size && size[0] >= 1.0 && size[1] >= 1.0 { 584 | imgui.example_size = size; 585 | let scale = &ui.io().display_framebuffer_scale; 586 | let texture_config = TextureConfig { 587 | size: Extent3d { 588 | width: (imgui.example_size[0] * scale[0]) as u32, 589 | height: (imgui.example_size[1] * scale[1]) as u32, 590 | ..Default::default() 591 | }, 592 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT 593 | | wgpu::TextureUsages::TEXTURE_BINDING, 594 | ..Default::default() 595 | }; 596 | imgui.renderer.textures.replace( 597 | imgui.example_texture_id, 598 | Texture::new(&window.device, &imgui.renderer, texture_config), 599 | ); 600 | } 601 | 602 | // Only render example to example_texture if the window is not collapsed 603 | imgui.example.setup_camera(&window.queue, size); 604 | imgui.example.render( 605 | imgui 606 | .renderer 607 | .textures 608 | .get(imgui.example_texture_id) 609 | .unwrap() 610 | .view(), 611 | &window.device, 612 | &window.queue, 613 | ); 614 | } 615 | 616 | let mut encoder: wgpu::CommandEncoder = window 617 | .device 618 | .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); 619 | 620 | if imgui.last_cursor != ui.mouse_cursor() { 621 | imgui.last_cursor = ui.mouse_cursor(); 622 | imgui.platform.prepare_render(ui, &window.window); 623 | } 624 | 625 | let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 626 | label: None, 627 | color_attachments: &[Some(wgpu::RenderPassColorAttachment { 628 | view: &view, 629 | resolve_target: None, 630 | ops: wgpu::Operations { 631 | load: wgpu::LoadOp::Load, // Do not clear 632 | // load: wgpu::LoadOp::Clear(clear_color), 633 | store: wgpu::StoreOp::Store, 634 | }, 635 | })], 636 | depth_stencil_attachment: None, 637 | timestamp_writes: None, 638 | occlusion_query_set: None, 639 | }); 640 | 641 | imgui 642 | .renderer 643 | .render( 644 | imgui.context.render(), 645 | &window.queue, 646 | &window.device, 647 | &mut rpass, 648 | ) 649 | .expect("Rendering failed"); 650 | 651 | drop(rpass); 652 | 653 | window.queue.submit(Some(encoder.finish())); 654 | frame.present(); 655 | } 656 | _ => (), 657 | } 658 | 659 | imgui.platform.handle_event::<()>( 660 | imgui.context.io_mut(), 661 | &window.window, 662 | &Event::WindowEvent { window_id, event }, 663 | ); 664 | } 665 | 666 | fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: ()) { 667 | let window = self.window.as_mut().unwrap(); 668 | let imgui = window.imgui.as_mut().unwrap(); 669 | imgui.platform.handle_event::<()>( 670 | imgui.context.io_mut(), 671 | &window.window, 672 | &Event::UserEvent(event), 673 | ); 674 | } 675 | 676 | fn device_event( 677 | &mut self, 678 | _event_loop: &ActiveEventLoop, 679 | device_id: winit::event::DeviceId, 680 | event: winit::event::DeviceEvent, 681 | ) { 682 | let window = self.window.as_mut().unwrap(); 683 | let imgui = window.imgui.as_mut().unwrap(); 684 | imgui.platform.handle_event::<()>( 685 | imgui.context.io_mut(), 686 | &window.window, 687 | &Event::DeviceEvent { device_id, event }, 688 | ); 689 | } 690 | 691 | fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { 692 | let window = self.window.as_mut().unwrap(); 693 | let imgui = window.imgui.as_mut().unwrap(); 694 | window.window.request_redraw(); 695 | imgui.platform.handle_event::<()>( 696 | imgui.context.io_mut(), 697 | &window.window, 698 | &Event::AboutToWait, 699 | ); 700 | } 701 | } 702 | 703 | fn main() { 704 | env_logger::init(); 705 | 706 | let event_loop = EventLoop::new().unwrap(); 707 | event_loop.set_control_flow(ControlFlow::Poll); 708 | event_loop.run_app(&mut App::default()).unwrap(); 709 | } 710 | -------------------------------------------------------------------------------- /examples/custom-texture.rs: -------------------------------------------------------------------------------- 1 | use image::ImageFormat; 2 | use imgui::*; 3 | use imgui_wgpu::{Renderer, RendererConfig, Texture, TextureConfig}; 4 | use imgui_winit_support::WinitPlatform; 5 | use pollster::block_on; 6 | use std::{sync::Arc, time::Instant}; 7 | use wgpu::Extent3d; 8 | use winit::{ 9 | application::ApplicationHandler, 10 | dpi::LogicalSize, 11 | event::{Event, WindowEvent}, 12 | event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, 13 | keyboard::{Key, NamedKey}, 14 | window::Window, 15 | }; 16 | 17 | struct ImguiState { 18 | context: imgui::Context, 19 | platform: WinitPlatform, 20 | renderer: Renderer, 21 | clear_color: wgpu::Color, 22 | width: u32, 23 | height: u32, 24 | checker_texture_id: TextureId, 25 | last_frame: Instant, 26 | last_cursor: Option, 27 | } 28 | 29 | struct AppWindow { 30 | device: wgpu::Device, 31 | queue: wgpu::Queue, 32 | window: Arc, 33 | surface_desc: wgpu::SurfaceConfiguration, 34 | surface: wgpu::Surface<'static>, 35 | hidpi_factor: f64, 36 | imgui: Option, 37 | } 38 | 39 | #[derive(Default)] 40 | struct App { 41 | window: Option, 42 | } 43 | 44 | impl AppWindow { 45 | fn setup_gpu(event_loop: &ActiveEventLoop) -> Self { 46 | let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { 47 | backends: wgpu::Backends::PRIMARY, 48 | ..Default::default() 49 | }); 50 | 51 | let window = { 52 | let version = env!("CARGO_PKG_VERSION"); 53 | 54 | let size = LogicalSize::new(1280.0, 720.0); 55 | 56 | let attributes = Window::default_attributes() 57 | .with_inner_size(size) 58 | .with_title(format!("imgui-wgpu {version}")); 59 | Arc::new(event_loop.create_window(attributes).unwrap()) 60 | }; 61 | 62 | let size = window.inner_size(); 63 | let hidpi_factor = window.scale_factor(); 64 | let surface = instance.create_surface(window.clone()).unwrap(); 65 | 66 | let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { 67 | power_preference: wgpu::PowerPreference::HighPerformance, 68 | compatible_surface: Some(&surface), 69 | force_fallback_adapter: false, 70 | })) 71 | .unwrap(); 72 | 73 | let (device, queue) = 74 | block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).unwrap(); 75 | 76 | // Set up swap chain 77 | let surface_desc = wgpu::SurfaceConfiguration { 78 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT, 79 | format: wgpu::TextureFormat::Bgra8UnormSrgb, 80 | width: size.width, 81 | height: size.height, 82 | present_mode: wgpu::PresentMode::Fifo, 83 | desired_maximum_frame_latency: 2, 84 | alpha_mode: wgpu::CompositeAlphaMode::Auto, 85 | view_formats: vec![wgpu::TextureFormat::Bgra8Unorm], 86 | }; 87 | 88 | surface.configure(&device, &surface_desc); 89 | 90 | let imgui = None; 91 | Self { 92 | device, 93 | queue, 94 | window, 95 | surface_desc, 96 | surface, 97 | hidpi_factor, 98 | imgui, 99 | } 100 | } 101 | 102 | fn setup_imgui(&mut self) { 103 | let mut context = imgui::Context::create(); 104 | let mut platform = imgui_winit_support::WinitPlatform::new(&mut context); 105 | platform.attach_window( 106 | context.io_mut(), 107 | &self.window, 108 | imgui_winit_support::HiDpiMode::Default, 109 | ); 110 | context.set_ini_filename(None); 111 | 112 | let font_size = (13.0 * self.hidpi_factor) as f32; 113 | context.io_mut().font_global_scale = (1.0 / self.hidpi_factor) as f32; 114 | 115 | context.fonts().add_font(&[FontSource::DefaultFontData { 116 | config: Some(imgui::FontConfig { 117 | oversample_h: 1, 118 | pixel_snap_h: true, 119 | size_pixels: font_size, 120 | ..Default::default() 121 | }), 122 | }]); 123 | 124 | // 125 | // Set up dear imgui wgpu renderer 126 | // 127 | let clear_color = wgpu::Color { 128 | r: 0.1, 129 | g: 0.2, 130 | b: 0.3, 131 | a: 1.0, 132 | }; 133 | 134 | let renderer_config = RendererConfig { 135 | texture_format: self.surface_desc.format, 136 | ..Default::default() 137 | }; 138 | 139 | let mut renderer = Renderer::new(&mut context, &self.device, &self.queue, renderer_config); 140 | 141 | let last_frame = Instant::now(); 142 | let last_cursor = None; 143 | 144 | let checker_bytes = include_bytes!("../resources/checker.png"); 145 | let image = image::load_from_memory_with_format(checker_bytes, ImageFormat::Png) 146 | .expect("invalid image"); 147 | let image = image.to_rgba8(); 148 | let (width, height) = image.dimensions(); 149 | let raw_data = image.into_raw(); 150 | 151 | let texture_config = TextureConfig { 152 | size: Extent3d { 153 | width, 154 | height, 155 | ..Default::default() 156 | }, 157 | label: Some("checker texture"), 158 | format: Some(wgpu::TextureFormat::Rgba8Unorm), 159 | ..Default::default() 160 | }; 161 | 162 | let texture = Texture::new(&self.device, &renderer, texture_config); 163 | 164 | texture.write(&self.queue, &raw_data, width, height); 165 | let checker_texture_id = renderer.textures.insert(texture); 166 | 167 | self.imgui = Some(ImguiState { 168 | context, 169 | platform, 170 | renderer, 171 | clear_color, 172 | width, 173 | height, 174 | checker_texture_id, 175 | last_frame, 176 | last_cursor, 177 | }) 178 | } 179 | 180 | fn new(event_loop: &ActiveEventLoop) -> Self { 181 | let mut window = Self::setup_gpu(event_loop); 182 | window.setup_imgui(); 183 | window 184 | } 185 | } 186 | 187 | impl ApplicationHandler for App { 188 | fn resumed(&mut self, event_loop: &ActiveEventLoop) { 189 | self.window = Some(AppWindow::new(event_loop)); 190 | } 191 | 192 | fn window_event( 193 | &mut self, 194 | event_loop: &ActiveEventLoop, 195 | window_id: winit::window::WindowId, 196 | event: WindowEvent, 197 | ) { 198 | let window = self.window.as_mut().unwrap(); 199 | let imgui = window.imgui.as_mut().unwrap(); 200 | 201 | match &event { 202 | WindowEvent::Resized(size) => { 203 | window.surface_desc = wgpu::SurfaceConfiguration { 204 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT, 205 | format: wgpu::TextureFormat::Bgra8UnormSrgb, 206 | width: size.width, 207 | height: size.height, 208 | present_mode: wgpu::PresentMode::Fifo, 209 | desired_maximum_frame_latency: 2, 210 | alpha_mode: wgpu::CompositeAlphaMode::Auto, 211 | view_formats: vec![wgpu::TextureFormat::Bgra8Unorm], 212 | }; 213 | 214 | window 215 | .surface 216 | .configure(&window.device, &window.surface_desc); 217 | } 218 | WindowEvent::CloseRequested => event_loop.exit(), 219 | WindowEvent::KeyboardInput { event, .. } => { 220 | if let Key::Named(NamedKey::Escape) = event.logical_key { 221 | if event.state.is_pressed() { 222 | event_loop.exit(); 223 | } 224 | } 225 | } 226 | WindowEvent::RedrawRequested => { 227 | let now = Instant::now(); 228 | imgui 229 | .context 230 | .io_mut() 231 | .update_delta_time(now - imgui.last_frame); 232 | imgui.last_frame = now; 233 | 234 | let frame = match window.surface.get_current_texture() { 235 | Ok(frame) => frame, 236 | Err(e) => { 237 | eprintln!("dropped frame: {e:?}"); 238 | return; 239 | } 240 | }; 241 | imgui 242 | .platform 243 | .prepare_frame(imgui.context.io_mut(), &window.window) 244 | .expect("Failed to prepare frame"); 245 | let ui = imgui.context.frame(); 246 | 247 | { 248 | let size = [imgui.width as f32, imgui.height as f32]; 249 | let window = ui.window("Hello world"); 250 | window 251 | .size([400.0, 600.0], Condition::FirstUseEver) 252 | .build(|| { 253 | ui.text("Hello textures!"); 254 | ui.text("Say hello to checker.png"); 255 | Image::new(imgui.checker_texture_id, size).build(ui); 256 | }); 257 | } 258 | 259 | let mut encoder: wgpu::CommandEncoder = window 260 | .device 261 | .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); 262 | 263 | if imgui.last_cursor != ui.mouse_cursor() { 264 | imgui.last_cursor = ui.mouse_cursor(); 265 | imgui.platform.prepare_render(ui, &window.window); 266 | } 267 | 268 | let view = frame 269 | .texture 270 | .create_view(&wgpu::TextureViewDescriptor::default()); 271 | let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 272 | label: None, 273 | color_attachments: &[Some(wgpu::RenderPassColorAttachment { 274 | view: &view, 275 | resolve_target: None, 276 | ops: wgpu::Operations { 277 | load: wgpu::LoadOp::Clear(imgui.clear_color), 278 | store: wgpu::StoreOp::Store, 279 | }, 280 | })], 281 | depth_stencil_attachment: None, 282 | timestamp_writes: None, 283 | occlusion_query_set: None, 284 | }); 285 | 286 | imgui 287 | .renderer 288 | .render( 289 | imgui.context.render(), 290 | &window.queue, 291 | &window.device, 292 | &mut rpass, 293 | ) 294 | .expect("Rendering failed"); 295 | 296 | drop(rpass); 297 | 298 | window.queue.submit(Some(encoder.finish())); 299 | frame.present(); 300 | } 301 | _ => (), 302 | } 303 | 304 | imgui.platform.handle_event::<()>( 305 | imgui.context.io_mut(), 306 | &window.window, 307 | &Event::WindowEvent { window_id, event }, 308 | ); 309 | } 310 | 311 | fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: ()) { 312 | let window = self.window.as_mut().unwrap(); 313 | let imgui = window.imgui.as_mut().unwrap(); 314 | imgui.platform.handle_event::<()>( 315 | imgui.context.io_mut(), 316 | &window.window, 317 | &Event::UserEvent(event), 318 | ); 319 | } 320 | 321 | fn device_event( 322 | &mut self, 323 | _event_loop: &ActiveEventLoop, 324 | device_id: winit::event::DeviceId, 325 | event: winit::event::DeviceEvent, 326 | ) { 327 | let window = self.window.as_mut().unwrap(); 328 | let imgui = window.imgui.as_mut().unwrap(); 329 | imgui.platform.handle_event::<()>( 330 | imgui.context.io_mut(), 331 | &window.window, 332 | &Event::DeviceEvent { device_id, event }, 333 | ); 334 | } 335 | 336 | fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { 337 | let window = self.window.as_mut().unwrap(); 338 | let imgui = window.imgui.as_mut().unwrap(); 339 | window.window.request_redraw(); 340 | imgui.platform.handle_event::<()>( 341 | imgui.context.io_mut(), 342 | &window.window, 343 | &Event::AboutToWait, 344 | ); 345 | } 346 | } 347 | 348 | fn main() { 349 | env_logger::init(); 350 | 351 | let event_loop = EventLoop::new().unwrap(); 352 | event_loop.set_control_flow(ControlFlow::Poll); 353 | event_loop.run_app(&mut App::default()).unwrap(); 354 | } 355 | -------------------------------------------------------------------------------- /examples/hello-world.rs: -------------------------------------------------------------------------------- 1 | use imgui::*; 2 | use imgui_wgpu::{Renderer, RendererConfig}; 3 | use imgui_winit_support::WinitPlatform; 4 | use pollster::block_on; 5 | use std::{sync::Arc, time::Instant}; 6 | use winit::{ 7 | application::ApplicationHandler, 8 | dpi::LogicalSize, 9 | event::{Event, WindowEvent}, 10 | event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, 11 | keyboard::{Key, NamedKey}, 12 | window::Window, 13 | }; 14 | 15 | struct ImguiState { 16 | context: imgui::Context, 17 | platform: WinitPlatform, 18 | renderer: Renderer, 19 | clear_color: wgpu::Color, 20 | demo_open: bool, 21 | last_frame: Instant, 22 | last_cursor: Option, 23 | } 24 | 25 | struct AppWindow { 26 | device: wgpu::Device, 27 | queue: wgpu::Queue, 28 | window: Arc, 29 | surface_desc: wgpu::SurfaceConfiguration, 30 | surface: wgpu::Surface<'static>, 31 | hidpi_factor: f64, 32 | imgui: Option, 33 | } 34 | 35 | #[derive(Default)] 36 | struct App { 37 | window: Option, 38 | } 39 | 40 | impl AppWindow { 41 | fn setup_gpu(event_loop: &ActiveEventLoop) -> Self { 42 | let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { 43 | backends: wgpu::Backends::PRIMARY, 44 | ..Default::default() 45 | }); 46 | 47 | let window = { 48 | let version = env!("CARGO_PKG_VERSION"); 49 | 50 | let size = LogicalSize::new(1280.0, 720.0); 51 | 52 | let attributes = Window::default_attributes() 53 | .with_inner_size(size) 54 | .with_title(format!("imgui-wgpu {version}")); 55 | Arc::new(event_loop.create_window(attributes).unwrap()) 56 | }; 57 | 58 | let size = window.inner_size(); 59 | let hidpi_factor = window.scale_factor(); 60 | let surface = instance.create_surface(window.clone()).unwrap(); 61 | 62 | let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { 63 | power_preference: wgpu::PowerPreference::HighPerformance, 64 | compatible_surface: Some(&surface), 65 | force_fallback_adapter: false, 66 | })) 67 | .unwrap(); 68 | 69 | let (device, queue) = 70 | block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).unwrap(); 71 | 72 | // Set up swap chain 73 | let surface_desc = wgpu::SurfaceConfiguration { 74 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT, 75 | format: wgpu::TextureFormat::Bgra8UnormSrgb, 76 | width: size.width, 77 | height: size.height, 78 | present_mode: wgpu::PresentMode::Fifo, 79 | desired_maximum_frame_latency: 2, 80 | alpha_mode: wgpu::CompositeAlphaMode::Auto, 81 | view_formats: vec![wgpu::TextureFormat::Bgra8Unorm], 82 | }; 83 | 84 | surface.configure(&device, &surface_desc); 85 | 86 | let imgui = None; 87 | Self { 88 | device, 89 | queue, 90 | window, 91 | surface_desc, 92 | surface, 93 | hidpi_factor, 94 | imgui, 95 | } 96 | } 97 | 98 | fn setup_imgui(&mut self) { 99 | let mut context = imgui::Context::create(); 100 | let mut platform = imgui_winit_support::WinitPlatform::new(&mut context); 101 | platform.attach_window( 102 | context.io_mut(), 103 | &self.window, 104 | imgui_winit_support::HiDpiMode::Default, 105 | ); 106 | context.set_ini_filename(None); 107 | 108 | let font_size = (13.0 * self.hidpi_factor) as f32; 109 | context.io_mut().font_global_scale = (1.0 / self.hidpi_factor) as f32; 110 | 111 | context.fonts().add_font(&[FontSource::DefaultFontData { 112 | config: Some(imgui::FontConfig { 113 | oversample_h: 1, 114 | pixel_snap_h: true, 115 | size_pixels: font_size, 116 | ..Default::default() 117 | }), 118 | }]); 119 | 120 | // 121 | // Set up dear imgui wgpu renderer 122 | // 123 | let clear_color = wgpu::Color { 124 | r: 0.1, 125 | g: 0.2, 126 | b: 0.3, 127 | a: 1.0, 128 | }; 129 | 130 | let renderer_config = RendererConfig { 131 | texture_format: self.surface_desc.format, 132 | ..Default::default() 133 | }; 134 | 135 | let renderer = Renderer::new(&mut context, &self.device, &self.queue, renderer_config); 136 | let last_frame = Instant::now(); 137 | let last_cursor = None; 138 | let demo_open = true; 139 | 140 | self.imgui = Some(ImguiState { 141 | context, 142 | platform, 143 | renderer, 144 | clear_color, 145 | demo_open, 146 | last_frame, 147 | last_cursor, 148 | }) 149 | } 150 | 151 | fn new(event_loop: &ActiveEventLoop) -> Self { 152 | let mut window = Self::setup_gpu(event_loop); 153 | window.setup_imgui(); 154 | window 155 | } 156 | } 157 | 158 | impl ApplicationHandler for App { 159 | fn resumed(&mut self, event_loop: &ActiveEventLoop) { 160 | self.window = Some(AppWindow::new(event_loop)); 161 | } 162 | 163 | fn window_event( 164 | &mut self, 165 | event_loop: &ActiveEventLoop, 166 | window_id: winit::window::WindowId, 167 | event: WindowEvent, 168 | ) { 169 | let window = self.window.as_mut().unwrap(); 170 | let imgui = window.imgui.as_mut().unwrap(); 171 | 172 | match &event { 173 | WindowEvent::Resized(size) => { 174 | window.surface_desc = wgpu::SurfaceConfiguration { 175 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT, 176 | format: wgpu::TextureFormat::Bgra8UnormSrgb, 177 | width: size.width, 178 | height: size.height, 179 | present_mode: wgpu::PresentMode::Fifo, 180 | desired_maximum_frame_latency: 2, 181 | alpha_mode: wgpu::CompositeAlphaMode::Auto, 182 | view_formats: vec![wgpu::TextureFormat::Bgra8Unorm], 183 | }; 184 | 185 | window 186 | .surface 187 | .configure(&window.device, &window.surface_desc); 188 | } 189 | WindowEvent::CloseRequested => event_loop.exit(), 190 | WindowEvent::KeyboardInput { event, .. } => { 191 | if let Key::Named(NamedKey::Escape) = event.logical_key { 192 | if event.state.is_pressed() { 193 | event_loop.exit(); 194 | } 195 | } 196 | } 197 | WindowEvent::RedrawRequested => { 198 | let delta_s = imgui.last_frame.elapsed(); 199 | let now = Instant::now(); 200 | imgui 201 | .context 202 | .io_mut() 203 | .update_delta_time(now - imgui.last_frame); 204 | imgui.last_frame = now; 205 | 206 | let frame = match window.surface.get_current_texture() { 207 | Ok(frame) => frame, 208 | Err(e) => { 209 | eprintln!("dropped frame: {e:?}"); 210 | return; 211 | } 212 | }; 213 | imgui 214 | .platform 215 | .prepare_frame(imgui.context.io_mut(), &window.window) 216 | .expect("Failed to prepare frame"); 217 | let ui = imgui.context.frame(); 218 | 219 | { 220 | let window = ui.window("Hello world"); 221 | window 222 | .size([300.0, 100.0], Condition::FirstUseEver) 223 | .build(|| { 224 | ui.text("Hello world!"); 225 | ui.text("This...is...imgui-rs on WGPU!"); 226 | ui.separator(); 227 | let mouse_pos = ui.io().mouse_pos; 228 | ui.text(format!( 229 | "Mouse Position: ({:.1},{:.1})", 230 | mouse_pos[0], mouse_pos[1] 231 | )); 232 | }); 233 | 234 | let window = ui.window("Hello too"); 235 | window 236 | .size([400.0, 200.0], Condition::FirstUseEver) 237 | .position([400.0, 200.0], Condition::FirstUseEver) 238 | .build(|| { 239 | ui.text(format!("Frametime: {delta_s:?}")); 240 | }); 241 | 242 | ui.show_demo_window(&mut imgui.demo_open); 243 | } 244 | 245 | let mut encoder: wgpu::CommandEncoder = window 246 | .device 247 | .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); 248 | 249 | if imgui.last_cursor != ui.mouse_cursor() { 250 | imgui.last_cursor = ui.mouse_cursor(); 251 | imgui.platform.prepare_render(ui, &window.window); 252 | } 253 | 254 | let view = frame 255 | .texture 256 | .create_view(&wgpu::TextureViewDescriptor::default()); 257 | let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 258 | label: None, 259 | color_attachments: &[Some(wgpu::RenderPassColorAttachment { 260 | view: &view, 261 | resolve_target: None, 262 | ops: wgpu::Operations { 263 | load: wgpu::LoadOp::Clear(imgui.clear_color), 264 | store: wgpu::StoreOp::Store, 265 | }, 266 | })], 267 | depth_stencil_attachment: None, 268 | timestamp_writes: None, 269 | occlusion_query_set: None, 270 | }); 271 | 272 | imgui 273 | .renderer 274 | .render( 275 | imgui.context.render(), 276 | &window.queue, 277 | &window.device, 278 | &mut rpass, 279 | ) 280 | .expect("Rendering failed"); 281 | 282 | drop(rpass); 283 | 284 | window.queue.submit(Some(encoder.finish())); 285 | 286 | frame.present(); 287 | } 288 | _ => (), 289 | } 290 | 291 | imgui.platform.handle_event::<()>( 292 | imgui.context.io_mut(), 293 | &window.window, 294 | &Event::WindowEvent { window_id, event }, 295 | ); 296 | } 297 | 298 | fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: ()) { 299 | let window = self.window.as_mut().unwrap(); 300 | let imgui = window.imgui.as_mut().unwrap(); 301 | imgui.platform.handle_event::<()>( 302 | imgui.context.io_mut(), 303 | &window.window, 304 | &Event::UserEvent(event), 305 | ); 306 | } 307 | 308 | fn device_event( 309 | &mut self, 310 | _event_loop: &ActiveEventLoop, 311 | device_id: winit::event::DeviceId, 312 | event: winit::event::DeviceEvent, 313 | ) { 314 | let window = self.window.as_mut().unwrap(); 315 | let imgui = window.imgui.as_mut().unwrap(); 316 | imgui.platform.handle_event::<()>( 317 | imgui.context.io_mut(), 318 | &window.window, 319 | &Event::DeviceEvent { device_id, event }, 320 | ); 321 | } 322 | 323 | fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { 324 | let window = self.window.as_mut().unwrap(); 325 | let imgui = window.imgui.as_mut().unwrap(); 326 | window.window.request_redraw(); 327 | imgui.platform.handle_event::<()>( 328 | imgui.context.io_mut(), 329 | &window.window, 330 | &Event::AboutToWait, 331 | ); 332 | } 333 | } 334 | 335 | fn main() { 336 | env_logger::init(); 337 | 338 | let event_loop = EventLoop::new().unwrap(); 339 | event_loop.set_control_flow(ControlFlow::Poll); 340 | event_loop.run_app(&mut App::default()).unwrap(); 341 | } 342 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | consolidate-commits = true 2 | dev-version = false 3 | tag = false 4 | pre-release-commit-message = "Bump version for release" 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended", "schedule:weekly"], 4 | "dependencyDashboard": true, 5 | "prConcurrentLimit": 20, 6 | "prHourlyLimit": 200, 7 | "labels": ["dependencies"], 8 | "lockFileMaintenance": { 9 | "enabled": true, 10 | "recreateWhen": "always", 11 | "rebaseWhen": "conflicted", 12 | "branchTopic": "Cargo.lock update", 13 | "commitMessageAction": "Update Cargo.lock", 14 | "schedule": ["before 4am on monday"], 15 | "prBodyDefinitions": { 16 | "Change": "All locks refreshed" 17 | } 18 | }, 19 | "packageRules": [ 20 | { 21 | "matchUpdateTypes": ["patch"], 22 | "matchCurrentVersion": "<1.0.0", 23 | "enabled": false, 24 | "description": "Patch updates to 0.x.y crates are compatible and handled by lockFileMaintenance" 25 | }, 26 | { 27 | "matchUpdateTypes": ["minor", "patch"], 28 | "matchCurrentVersion": ">=1.0.0", 29 | "enabled": false, 30 | "description": "Minor and patch updates to x.y.z crates are compatible and handled by lockFileMaintenance" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /resources/checker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yatekii/imgui-wgpu-rs/e55979ed671e1dcf2293538a863581fe4f776e09/resources/checker.png -------------------------------------------------------------------------------- /resources/cube.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexOutput { 2 | @location(0) tex_coord: vec2, 3 | @builtin(position) position: vec4, 4 | }; 5 | 6 | struct Locals { 7 | transform: mat4x4, 8 | }; 9 | @group(0) @binding(0) 10 | var r_locals: Locals; 11 | 12 | @vertex 13 | fn vs_main( 14 | @location(0) position: vec4, 15 | @location(1) tex_coord: vec2, 16 | ) -> VertexOutput { 17 | var out: VertexOutput; 18 | out.tex_coord = tex_coord; 19 | out.position = r_locals.transform * position; 20 | return out; 21 | } 22 | 23 | @group(0) @binding(1) 24 | var r_color: texture_2d; 25 | 26 | @fragment 27 | fn fs_main(in: VertexOutput) -> @location(0) vec4 { 28 | let tex = textureLoad(r_color, vec2(in.tex_coord * 256.0), 0); 29 | let v = f32(tex.x) / 255.0; 30 | return vec4(1.0 - (v * 5.0), 1.0 - (v * 15.0), 1.0 - (v * 50.0), 1.0); 31 | } 32 | 33 | @fragment 34 | fn fs_wire() -> @location(0) vec4 { 35 | return vec4(0.0, 0.5, 0.0, 0.5); 36 | } 37 | -------------------------------------------------------------------------------- /src/imgui.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | u_Matrix: mat4x4, 3 | }; 4 | 5 | struct VertexInput { 6 | @location(0) a_Pos: vec2, 7 | @location(1) a_UV: vec2, 8 | @location(2) a_Color: vec4, 9 | }; 10 | 11 | struct VertexOutput { 12 | @location(0) v_UV: vec2, 13 | @location(1) v_Color: vec4, 14 | @builtin(position) v_Position: vec4, 15 | }; 16 | 17 | @group(0) @binding(0) 18 | var uniforms: Uniforms; 19 | 20 | @vertex 21 | fn vs_main(in: VertexInput) -> VertexOutput { 22 | var out: VertexOutput; 23 | out.v_UV = in.a_UV; 24 | out.v_Color = in.a_Color; 25 | out.v_Position = uniforms.u_Matrix * vec4(in.a_Pos.xy, 0.0, 1.0); 26 | return out; 27 | } 28 | 29 | struct FragmentOutput { 30 | @location(0) o_Target: vec4, 31 | }; 32 | 33 | @group(1) @binding(0) 34 | var u_Texture: texture_2d; 35 | @group(1) @binding(1) 36 | var u_Sampler: sampler; 37 | 38 | fn srgb_to_linear(srgb: vec4) -> vec4 { 39 | let color_srgb = srgb.rgb; 40 | let selector = ceil(color_srgb - 0.04045); // 0 if under value, 1 if over 41 | let under = color_srgb / 12.92; 42 | let over = pow((color_srgb + 0.055) / 1.055, vec3(2.4)); 43 | let result = mix(under, over, selector); 44 | return vec4(result, srgb.a); 45 | } 46 | 47 | @fragment 48 | fn fs_main_linear(in: VertexOutput) -> FragmentOutput { 49 | let color = srgb_to_linear(in.v_Color); 50 | 51 | return FragmentOutput(color * textureSample(u_Texture, u_Sampler, in.v_UV)); 52 | } 53 | 54 | @fragment 55 | fn fs_main_srgb(in: VertexOutput) -> FragmentOutput { 56 | let color = in.v_Color; 57 | 58 | return FragmentOutput(color * textureSample(u_Texture, u_Sampler, in.v_UV)); 59 | } 60 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use imgui::{ 2 | Context, DrawCmd::Elements, DrawData, DrawIdx, DrawList, DrawVert, TextureId, Textures, 3 | }; 4 | use smallvec::SmallVec; 5 | use std::error::Error; 6 | use std::fmt; 7 | use std::mem::size_of; 8 | use std::sync::Arc; 9 | use wgpu::util::{BufferInitDescriptor, DeviceExt}; 10 | use wgpu::*; 11 | 12 | static VS_ENTRY_POINT: &str = "vs_main"; 13 | static FS_ENTRY_POINT_LINEAR: &str = "fs_main_linear"; 14 | static FS_ENTRY_POINT_SRGB: &str = "fs_main_srgb"; 15 | 16 | pub type RendererResult = Result; 17 | 18 | #[repr(transparent)] 19 | #[derive(Debug, Copy, Clone)] 20 | struct DrawVertPod(DrawVert); 21 | 22 | unsafe impl bytemuck::Zeroable for DrawVertPod {} 23 | 24 | unsafe impl bytemuck::Pod for DrawVertPod {} 25 | 26 | #[derive(Clone, Debug)] 27 | pub enum RendererError { 28 | BadTexture(TextureId), 29 | } 30 | 31 | impl fmt::Display for RendererError { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | match *self { 34 | RendererError::BadTexture(id) => { 35 | write!(f, "imgui render error: bad texture id '{}'", id.id()) 36 | } 37 | } 38 | } 39 | } 40 | 41 | impl Error for RendererError {} 42 | 43 | #[allow(dead_code)] 44 | enum ShaderStage { 45 | Vertex, 46 | Fragment, 47 | Compute, 48 | } 49 | 50 | /// Config for creating a texture from raw parts 51 | /// 52 | #[derive(Clone)] 53 | pub struct RawTextureConfig<'a> { 54 | /// An optional label for the bind group used for debugging. 55 | pub label: Option<&'a str>, 56 | /// The sampler descriptor of the texture. 57 | pub sampler_desc: SamplerDescriptor<'a>, 58 | } 59 | 60 | /// Config for creating a texture. 61 | /// 62 | /// Uses the builder pattern. 63 | #[derive(Clone)] 64 | pub struct TextureConfig<'a> { 65 | /// The size of the texture. 66 | pub size: Extent3d, 67 | /// An optional label for the texture used for debugging. 68 | pub label: Option<&'a str>, 69 | /// The format of the texture, if not set uses the format from the renderer. 70 | pub format: Option, 71 | /// The usage of the texture. 72 | pub usage: TextureUsages, 73 | /// The mip level of the texture. 74 | pub mip_level_count: u32, 75 | /// The sample count of the texture. 76 | pub sample_count: u32, 77 | /// The dimension of the texture. 78 | pub dimension: TextureDimension, 79 | /// The sampler descriptor of the texture. 80 | pub sampler_desc: SamplerDescriptor<'a>, 81 | } 82 | 83 | impl Default for TextureConfig<'_> { 84 | /// Create a new texture config. 85 | fn default() -> Self { 86 | let sampler_desc = SamplerDescriptor { 87 | label: Some("imgui-wgpu sampler"), 88 | address_mode_u: AddressMode::ClampToEdge, 89 | address_mode_v: AddressMode::ClampToEdge, 90 | address_mode_w: AddressMode::ClampToEdge, 91 | mag_filter: FilterMode::Linear, 92 | min_filter: FilterMode::Linear, 93 | mipmap_filter: FilterMode::Linear, 94 | lod_min_clamp: 0.0, 95 | lod_max_clamp: 100.0, 96 | compare: None, 97 | anisotropy_clamp: 1, 98 | border_color: None, 99 | }; 100 | 101 | Self { 102 | size: Extent3d { 103 | width: 0, 104 | height: 0, 105 | depth_or_array_layers: 1, 106 | }, 107 | label: None, 108 | format: None, 109 | usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, 110 | mip_level_count: 1, 111 | sample_count: 1, 112 | dimension: TextureDimension::D2, 113 | sampler_desc, 114 | } 115 | } 116 | } 117 | 118 | /// A container for a bindable texture. 119 | pub struct Texture { 120 | texture: Arc, 121 | view: Arc, 122 | bind_group: Arc, 123 | size: Extent3d, 124 | } 125 | 126 | impl Texture { 127 | /// Create a `Texture` from its raw parts. 128 | /// - `bind_group`: The bind group used by the texture. If it is `None`, the bind group will be created like in `Self::new`. 129 | /// - `config`: The config used for creating the bind group. If `bind_group` is `Some(_)`, it will be ignored 130 | pub fn from_raw_parts( 131 | device: &Device, 132 | renderer: &Renderer, 133 | texture: Arc, 134 | view: Arc, 135 | bind_group: Option>, 136 | config: Option<&RawTextureConfig>, 137 | size: Extent3d, 138 | ) -> Self { 139 | let bind_group = bind_group.unwrap_or_else(|| { 140 | let config = config.unwrap(); 141 | 142 | // Create the texture sampler. 143 | let sampler = device.create_sampler(&config.sampler_desc); 144 | 145 | // Create the texture bind group from the layout. 146 | Arc::new(device.create_bind_group(&BindGroupDescriptor { 147 | label: config.label, 148 | layout: &renderer.texture_layout, 149 | entries: &[ 150 | BindGroupEntry { 151 | binding: 0, 152 | resource: BindingResource::TextureView(&view), 153 | }, 154 | BindGroupEntry { 155 | binding: 1, 156 | resource: BindingResource::Sampler(&sampler), 157 | }, 158 | ], 159 | })) 160 | }); 161 | 162 | Self { 163 | texture, 164 | view, 165 | bind_group, 166 | size, 167 | } 168 | } 169 | 170 | /// Create a new GPU texture width the specified `config`. 171 | pub fn new(device: &Device, renderer: &Renderer, config: TextureConfig) -> Self { 172 | // Create the wgpu texture. 173 | let texture = Arc::new(device.create_texture(&TextureDescriptor { 174 | label: config.label, 175 | size: config.size, 176 | mip_level_count: config.mip_level_count, 177 | sample_count: config.sample_count, 178 | dimension: config.dimension, 179 | format: config.format.unwrap_or(renderer.config.texture_format), 180 | usage: config.usage, 181 | view_formats: &[config.format.unwrap_or(renderer.config.texture_format)], 182 | })); 183 | 184 | // Extract the texture view. 185 | let view = Arc::new(texture.create_view(&TextureViewDescriptor::default())); 186 | 187 | // Create the texture sampler. 188 | let sampler = device.create_sampler(&config.sampler_desc); 189 | 190 | // Create the texture bind group from the layout. 191 | let bind_group = Arc::new(device.create_bind_group(&BindGroupDescriptor { 192 | label: config.label, 193 | layout: &renderer.texture_layout, 194 | entries: &[ 195 | BindGroupEntry { 196 | binding: 0, 197 | resource: BindingResource::TextureView(&view), 198 | }, 199 | BindGroupEntry { 200 | binding: 1, 201 | resource: BindingResource::Sampler(&sampler), 202 | }, 203 | ], 204 | })); 205 | 206 | Self { 207 | texture, 208 | view, 209 | bind_group, 210 | size: config.size, 211 | } 212 | } 213 | 214 | /// Write `data` to the texture. 215 | /// 216 | /// - `data`: 32-bit RGBA bitmap data. 217 | /// - `width`: The width of the source bitmap (`data`) in pixels. 218 | /// - `height`: The height of the source bitmap (`data`) in pixels. 219 | pub fn write(&self, queue: &Queue, data: &[u8], width: u32, height: u32) { 220 | queue.write_texture( 221 | // destination (sub)texture 222 | TexelCopyTextureInfo { 223 | texture: &self.texture, 224 | mip_level: 0, 225 | origin: Origin3d { x: 0, y: 0, z: 0 }, 226 | aspect: TextureAspect::All, 227 | }, 228 | // source bitmap data 229 | data, 230 | // layout of the source bitmap 231 | TexelCopyBufferLayout { 232 | offset: 0, 233 | bytes_per_row: Some(width * 4), 234 | rows_per_image: Some(height), 235 | }, 236 | // size of the source bitmap 237 | Extent3d { 238 | width, 239 | height, 240 | depth_or_array_layers: 1, 241 | }, 242 | ); 243 | } 244 | 245 | /// The width of the texture in pixels. 246 | pub fn width(&self) -> u32 { 247 | self.size.width 248 | } 249 | 250 | /// The height of the texture in pixels. 251 | pub fn height(&self) -> u32 { 252 | self.size.height 253 | } 254 | 255 | /// The depth of the texture. 256 | pub fn depth(&self) -> u32 { 257 | self.size.depth_or_array_layers 258 | } 259 | 260 | /// The size of the texture in pixels. 261 | pub fn size(&self) -> Extent3d { 262 | self.size 263 | } 264 | 265 | /// The underlying `wgpu::Texture`. 266 | pub fn texture(&self) -> &wgpu::Texture { 267 | &self.texture 268 | } 269 | 270 | /// The `wgpu::TextureView` of the underlying texture. 271 | pub fn view(&self) -> &wgpu::TextureView { 272 | &self.view 273 | } 274 | } 275 | 276 | /// Configuration for the renderer. 277 | pub struct RendererConfig<'s> { 278 | pub texture_format: TextureFormat, 279 | pub depth_format: Option, 280 | pub sample_count: u32, 281 | pub shader: Option>, 282 | pub vertex_shader_entry_point: Option<&'s str>, 283 | pub fragment_shader_entry_point: Option<&'s str>, 284 | } 285 | 286 | impl<'s> RendererConfig<'s> { 287 | /// Create a new renderer config with custom shaders. 288 | pub fn with_shaders(shader: ShaderModuleDescriptor<'s>) -> Self { 289 | RendererConfig { 290 | texture_format: TextureFormat::Rgba8Unorm, 291 | depth_format: None, 292 | sample_count: 1, 293 | shader: Some(shader), 294 | vertex_shader_entry_point: Some(VS_ENTRY_POINT), 295 | fragment_shader_entry_point: Some(FS_ENTRY_POINT_LINEAR), 296 | } 297 | } 298 | } 299 | 300 | impl Default for RendererConfig<'_> { 301 | /// Create a new renderer config with precompiled default shaders outputting linear color. 302 | /// 303 | /// If you write to a Bgra8UnormSrgb framebuffer, this is what you want. 304 | fn default() -> Self { 305 | Self::new() 306 | } 307 | } 308 | 309 | impl RendererConfig<'_> { 310 | /// Create a new renderer config with precompiled default shaders outputting linear color. 311 | /// 312 | /// If you write to a Bgra8UnormSrgb framebuffer, this is what you want. 313 | pub fn new() -> Self { 314 | RendererConfig { 315 | fragment_shader_entry_point: Some(FS_ENTRY_POINT_LINEAR), 316 | ..Self::with_shaders(include_wgsl!("imgui.wgsl")) 317 | } 318 | } 319 | 320 | /// Create a new renderer config with precompiled default shaders outputting srgb color. 321 | /// 322 | /// If you write to a Bgra8Unorm framebuffer, this is what you want. 323 | pub fn new_srgb() -> Self { 324 | RendererConfig { 325 | fragment_shader_entry_point: Some(FS_ENTRY_POINT_SRGB), 326 | ..Self::with_shaders(include_wgsl!("imgui.wgsl")) 327 | } 328 | } 329 | } 330 | 331 | pub struct RenderData { 332 | fb_size: [f32; 2], 333 | last_size: [f32; 2], 334 | last_pos: [f32; 2], 335 | vertex_buffer: Option, 336 | vertex_buffer_size: usize, 337 | index_buffer: Option, 338 | index_buffer_size: usize, 339 | draw_list_offsets: SmallVec<[(i32, u32); 4]>, 340 | render: bool, 341 | } 342 | 343 | pub struct Renderer { 344 | pipeline: RenderPipeline, 345 | uniform_buffer: Buffer, 346 | uniform_bind_group: BindGroup, 347 | /// Textures of the font atlas and all images. 348 | pub textures: Textures, 349 | texture_layout: BindGroupLayout, 350 | render_data: Option, 351 | config: RendererConfig<'static>, 352 | } 353 | 354 | impl Renderer { 355 | /// Create an entirely new imgui wgpu renderer. 356 | pub fn new( 357 | imgui: &mut Context, 358 | device: &Device, 359 | queue: &Queue, 360 | config: RendererConfig, 361 | ) -> Self { 362 | let RendererConfig { 363 | texture_format, 364 | depth_format, 365 | sample_count, 366 | shader, 367 | vertex_shader_entry_point, 368 | fragment_shader_entry_point, 369 | } = config; 370 | 371 | // Load shaders. 372 | let shader_module = device.create_shader_module(shader.unwrap()); 373 | 374 | // Create the uniform matrix buffer. 375 | let size = 64; 376 | let uniform_buffer = device.create_buffer(&BufferDescriptor { 377 | label: Some("imgui-wgpu uniform buffer"), 378 | size, 379 | usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, 380 | mapped_at_creation: false, 381 | }); 382 | 383 | // Create the uniform matrix buffer bind group layout. 384 | let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { 385 | label: None, 386 | entries: &[BindGroupLayoutEntry { 387 | binding: 0, 388 | visibility: wgpu::ShaderStages::VERTEX, 389 | ty: BindingType::Buffer { 390 | ty: BufferBindingType::Uniform, 391 | has_dynamic_offset: false, 392 | min_binding_size: None, 393 | }, 394 | count: None, 395 | }], 396 | }); 397 | 398 | // Create the uniform matrix buffer bind group. 399 | let uniform_bind_group = device.create_bind_group(&BindGroupDescriptor { 400 | label: Some("imgui-wgpu bind group"), 401 | layout: &uniform_layout, 402 | entries: &[BindGroupEntry { 403 | binding: 0, 404 | resource: uniform_buffer.as_entire_binding(), 405 | }], 406 | }); 407 | 408 | // Create the texture layout for further usage. 409 | let texture_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { 410 | label: Some("imgui-wgpu bind group layout"), 411 | entries: &[ 412 | BindGroupLayoutEntry { 413 | binding: 0, 414 | visibility: wgpu::ShaderStages::FRAGMENT, 415 | ty: BindingType::Texture { 416 | multisampled: false, 417 | sample_type: TextureSampleType::Float { filterable: true }, 418 | view_dimension: TextureViewDimension::D2, 419 | }, 420 | count: None, 421 | }, 422 | BindGroupLayoutEntry { 423 | binding: 1, 424 | visibility: wgpu::ShaderStages::FRAGMENT, 425 | ty: BindingType::Sampler(wgpu::SamplerBindingType::Filtering), 426 | count: None, 427 | }, 428 | ], 429 | }); 430 | 431 | // Create the render pipeline layout. 432 | let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { 433 | label: Some("imgui-wgpu pipeline layout"), 434 | bind_group_layouts: &[&uniform_layout, &texture_layout], 435 | push_constant_ranges: &[], 436 | }); 437 | 438 | // Create the render pipeline. 439 | // Create the render pipeline. 440 | let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { 441 | label: Some("imgui-wgpu pipeline"), 442 | layout: Some(&pipeline_layout), 443 | vertex: VertexState { 444 | module: &shader_module, 445 | entry_point: vertex_shader_entry_point, 446 | compilation_options: Default::default(), 447 | buffers: &[VertexBufferLayout { 448 | array_stride: size_of::() as BufferAddress, 449 | step_mode: VertexStepMode::Vertex, 450 | attributes: &vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Unorm8x4], 451 | }], 452 | }, 453 | primitive: PrimitiveState { 454 | topology: PrimitiveTopology::TriangleList, 455 | strip_index_format: None, 456 | front_face: FrontFace::Cw, 457 | cull_mode: None, 458 | polygon_mode: PolygonMode::Fill, 459 | unclipped_depth: false, 460 | conservative: false, 461 | }, 462 | depth_stencil: depth_format.map(|format| wgpu::DepthStencilState { 463 | format, 464 | depth_write_enabled: false, 465 | depth_compare: wgpu::CompareFunction::Always, 466 | stencil: wgpu::StencilState::default(), 467 | bias: DepthBiasState::default(), 468 | }), 469 | multisample: MultisampleState { 470 | count: sample_count, 471 | ..Default::default() 472 | }, 473 | fragment: Some(FragmentState { 474 | module: &shader_module, 475 | entry_point: fragment_shader_entry_point, 476 | compilation_options: Default::default(), 477 | targets: &[Some(ColorTargetState { 478 | format: texture_format, 479 | blend: Some(BlendState { 480 | color: BlendComponent { 481 | src_factor: BlendFactor::SrcAlpha, 482 | dst_factor: BlendFactor::OneMinusSrcAlpha, 483 | operation: BlendOperation::Add, 484 | }, 485 | alpha: BlendComponent { 486 | src_factor: BlendFactor::OneMinusDstAlpha, 487 | dst_factor: BlendFactor::One, 488 | operation: BlendOperation::Add, 489 | }, 490 | }), 491 | write_mask: ColorWrites::ALL, 492 | })], 493 | }), 494 | multiview: None, 495 | cache: None, 496 | }); 497 | 498 | let mut renderer = Self { 499 | pipeline, 500 | uniform_buffer, 501 | uniform_bind_group, 502 | textures: Textures::new(), 503 | texture_layout, 504 | render_data: None, 505 | config: RendererConfig { 506 | texture_format, 507 | depth_format, 508 | sample_count, 509 | shader: None, 510 | vertex_shader_entry_point: None, 511 | fragment_shader_entry_point: None, 512 | }, 513 | }; 514 | 515 | // Immediately load the font texture to the GPU. 516 | renderer.reload_font_texture(imgui, device, queue); 517 | 518 | renderer 519 | } 520 | 521 | /// Prepares buffers for the current imgui frame. This must be 522 | /// called before `Renderer::split_render`, and its output must 523 | /// be passed to the render call. 524 | pub fn prepare( 525 | &self, 526 | draw_data: &DrawData, 527 | render_data: Option, 528 | queue: &Queue, 529 | device: &Device, 530 | ) -> RenderData { 531 | let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0]; 532 | let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1]; 533 | 534 | let mut render_data = render_data.unwrap_or_else(|| RenderData { 535 | fb_size: [fb_width, fb_height], 536 | last_size: [0.0, 0.0], 537 | last_pos: [0.0, 0.0], 538 | vertex_buffer: None, 539 | vertex_buffer_size: 0, 540 | index_buffer: None, 541 | index_buffer_size: 0, 542 | draw_list_offsets: SmallVec::<[_; 4]>::new(), 543 | render: false, 544 | }); 545 | 546 | // If the render area is <= 0, exit here and now. 547 | if fb_width <= 0.0 || fb_height <= 0.0 { 548 | render_data.render = false; 549 | return render_data; 550 | } else { 551 | render_data.render = true; 552 | } 553 | 554 | // Only update matrices if the size or position changes 555 | if (render_data.last_size[0] - draw_data.display_size[0]).abs() > f32::EPSILON 556 | || (render_data.last_size[1] - draw_data.display_size[1]).abs() > f32::EPSILON 557 | || (render_data.last_pos[0] - draw_data.display_pos[0]).abs() > f32::EPSILON 558 | || (render_data.last_pos[1] - draw_data.display_pos[1]).abs() > f32::EPSILON 559 | { 560 | render_data.fb_size = [fb_width, fb_height]; 561 | render_data.last_size = draw_data.display_size; 562 | render_data.last_pos = draw_data.display_pos; 563 | 564 | let width = draw_data.display_size[0]; 565 | let height = draw_data.display_size[1]; 566 | 567 | let offset_x = draw_data.display_pos[0] / width; 568 | let offset_y = draw_data.display_pos[1] / height; 569 | 570 | // Create and update the transform matrix for the current frame. 571 | // This is required to adapt to vulkan coordinates. 572 | let matrix = [ 573 | [2.0 / width, 0.0, 0.0, 0.0], 574 | [0.0, 2.0 / -height, 0.0, 0.0], 575 | [0.0, 0.0, 1.0, 0.0], 576 | [-1.0 - offset_x * 2.0, 1.0 + offset_y * 2.0, 0.0, 1.0], 577 | ]; 578 | self.update_uniform_buffer(queue, &matrix); 579 | } 580 | 581 | render_data.draw_list_offsets.clear(); 582 | 583 | let mut vertex_count = 0; 584 | let mut index_count = 0; 585 | for draw_list in draw_data.draw_lists() { 586 | render_data 587 | .draw_list_offsets 588 | .push((vertex_count as i32, index_count as u32)); 589 | vertex_count += draw_list.vtx_buffer().len(); 590 | index_count += draw_list.idx_buffer().len(); 591 | } 592 | 593 | let mut vertices = Vec::with_capacity(vertex_count * std::mem::size_of::()); 594 | let mut indices = Vec::with_capacity(index_count * std::mem::size_of::()); 595 | 596 | for draw_list in draw_data.draw_lists() { 597 | // Safety: DrawVertPod is #[repr(transparent)] over DrawVert and DrawVert _should_ be Pod. 598 | let vertices_pod: &[DrawVertPod] = unsafe { draw_list.transmute_vtx_buffer() }; 599 | vertices.extend_from_slice(bytemuck::cast_slice(vertices_pod)); 600 | indices.extend_from_slice(bytemuck::cast_slice(draw_list.idx_buffer())); 601 | } 602 | 603 | // Copies in wgpu must be padded to 4 byte alignment 604 | indices.resize( 605 | indices.len() + COPY_BUFFER_ALIGNMENT as usize 606 | - indices.len() % COPY_BUFFER_ALIGNMENT as usize, 607 | 0, 608 | ); 609 | 610 | // If the buffer is not created or is too small for the new indices, create a new buffer 611 | if render_data.index_buffer.is_none() || render_data.index_buffer_size < indices.len() { 612 | let buffer = device.create_buffer_init(&BufferInitDescriptor { 613 | label: Some("imgui-wgpu index buffer"), 614 | contents: &indices, 615 | usage: BufferUsages::INDEX | BufferUsages::COPY_DST, 616 | }); 617 | render_data.index_buffer = Some(buffer); 618 | render_data.index_buffer_size = indices.len(); 619 | } else if let Some(buffer) = render_data.index_buffer.as_ref() { 620 | // The buffer is large enough for the new indices, so reuse it 621 | queue.write_buffer(buffer, 0, &indices); 622 | } else { 623 | unreachable!() 624 | } 625 | 626 | // If the buffer is not created or is too small for the new vertices, create a new buffer 627 | if render_data.vertex_buffer.is_none() || render_data.vertex_buffer_size < vertices.len() { 628 | let buffer = device.create_buffer_init(&BufferInitDescriptor { 629 | label: Some("imgui-wgpu vertex buffer"), 630 | contents: &vertices, 631 | usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, 632 | }); 633 | render_data.vertex_buffer = Some(buffer); 634 | render_data.vertex_buffer_size = vertices.len(); 635 | } else if let Some(buffer) = render_data.vertex_buffer.as_ref() { 636 | // The buffer is large enough for the new vertices, so reuse it 637 | queue.write_buffer(buffer, 0, &vertices); 638 | } else { 639 | unreachable!() 640 | } 641 | 642 | render_data 643 | } 644 | 645 | /// Render the current imgui frame. `Renderer::prepare` must be 646 | /// called first, and the output render data must be kept for the 647 | /// lifetime of the renderpass. 648 | pub fn split_render<'r>( 649 | &'r self, 650 | draw_data: &DrawData, 651 | render_data: &'r RenderData, 652 | rpass: &mut RenderPass<'r>, 653 | ) -> RendererResult<()> { 654 | if !render_data.render { 655 | return Ok(()); 656 | } 657 | 658 | rpass.set_pipeline(&self.pipeline); 659 | rpass.set_bind_group(0, &self.uniform_bind_group, &[]); 660 | rpass.set_vertex_buffer(0, render_data.vertex_buffer.as_ref().unwrap().slice(..)); 661 | rpass.set_index_buffer( 662 | render_data.index_buffer.as_ref().unwrap().slice(..), 663 | IndexFormat::Uint16, 664 | ); 665 | 666 | // Execute all the imgui render work. 667 | for (draw_list, bases) in draw_data 668 | .draw_lists() 669 | .zip(render_data.draw_list_offsets.iter()) 670 | { 671 | self.render_draw_list( 672 | rpass, 673 | draw_list, 674 | render_data.fb_size, 675 | draw_data.display_pos, 676 | draw_data.framebuffer_scale, 677 | *bases, 678 | )?; 679 | } 680 | 681 | Ok(()) 682 | } 683 | 684 | /// Render the current imgui frame. 685 | pub fn render<'r>( 686 | &'r mut self, 687 | draw_data: &DrawData, 688 | queue: &Queue, 689 | device: &Device, 690 | rpass: &mut RenderPass<'r>, 691 | ) -> RendererResult<()> { 692 | let render_data = self.render_data.take(); 693 | self.render_data = Some(self.prepare(draw_data, render_data, queue, device)); 694 | self.split_render(draw_data, self.render_data.as_ref().unwrap(), rpass) 695 | } 696 | 697 | /// Render a given `DrawList` from imgui onto a wgpu frame. 698 | fn render_draw_list<'render>( 699 | &'render self, 700 | rpass: &mut RenderPass<'render>, 701 | draw_list: &DrawList, 702 | fb_size: [f32; 2], 703 | clip_off: [f32; 2], 704 | clip_scale: [f32; 2], 705 | (vertex_base, index_base): (i32, u32), 706 | ) -> RendererResult<()> { 707 | let mut start = index_base; 708 | 709 | for cmd in draw_list.commands() { 710 | if let Elements { count, cmd_params } = cmd { 711 | let clip_rect = [ 712 | (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0], 713 | (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1], 714 | (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0], 715 | (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1], 716 | ]; 717 | 718 | // Set the current texture bind group on the renderpass. 719 | let texture_id = cmd_params.texture_id; 720 | let tex = self 721 | .textures 722 | .get(texture_id) 723 | .ok_or(RendererError::BadTexture(texture_id))?; 724 | rpass.set_bind_group(1, Some(tex.bind_group.as_ref()), &[]); 725 | 726 | // Set scissors on the renderpass. 727 | let end = start + count as u32; 728 | if clip_rect[0] < fb_size[0] 729 | && clip_rect[1] < fb_size[1] 730 | && clip_rect[2] >= 0.0 731 | && clip_rect[3] >= 0.0 732 | { 733 | let scissors = ( 734 | clip_rect[0].max(0.0).floor() as u32, 735 | clip_rect[1].max(0.0).floor() as u32, 736 | (clip_rect[2].min(fb_size[0]) - clip_rect[0].max(0.0)) 737 | .abs() 738 | .ceil() as u32, 739 | (clip_rect[3].min(fb_size[1]) - clip_rect[1].max(0.0)) 740 | .abs() 741 | .ceil() as u32, 742 | ); 743 | 744 | // XXX: Work-around for wgpu issue [1] by only issuing draw 745 | // calls if the scissor rect is valid (by wgpu's flawed 746 | // logic). Regardless, a zero-width or zero-height scissor 747 | // is essentially a no-op render anyway, so just skip it. 748 | // [1]: https://github.com/gfx-rs/wgpu/issues/1750 749 | if scissors.2 > 0 && scissors.3 > 0 { 750 | rpass.set_scissor_rect(scissors.0, scissors.1, scissors.2, scissors.3); 751 | 752 | // Draw the current batch of vertices with the renderpass. 753 | rpass.draw_indexed(start..end, vertex_base, 0..1); 754 | } 755 | } 756 | 757 | // Increment the index regardless of whether or not this batch 758 | // of vertices was drawn. 759 | start = end; 760 | } 761 | } 762 | Ok(()) 763 | } 764 | 765 | /// Updates the current uniform buffer containing the transform matrix. 766 | fn update_uniform_buffer(&self, queue: &Queue, matrix: &[[f32; 4]; 4]) { 767 | let data = bytemuck::bytes_of(matrix); 768 | queue.write_buffer(&self.uniform_buffer, 0, data); 769 | } 770 | 771 | /// Updates the texture on the GPU corresponding to the current imgui font atlas. 772 | /// 773 | /// This has to be called after loading a font. 774 | pub fn reload_font_texture(&mut self, imgui: &mut Context, device: &Device, queue: &Queue) { 775 | let fonts = imgui.fonts(); 776 | // Remove possible font atlas texture. 777 | self.textures.remove(fonts.tex_id); 778 | 779 | // Create font texture and upload it. 780 | let handle = fonts.build_rgba32_texture(); 781 | let font_texture_cnfig = TextureConfig { 782 | label: Some("imgui-wgpu font atlas"), 783 | size: Extent3d { 784 | width: handle.width, 785 | height: handle.height, 786 | ..Default::default() 787 | }, 788 | ..Default::default() 789 | }; 790 | 791 | let font_texture = Texture::new(device, self, font_texture_cnfig); 792 | font_texture.write(queue, handle.data, handle.width, handle.height); 793 | fonts.tex_id = self.textures.insert(font_texture); 794 | // Clear imgui texture data to save memory. 795 | fonts.clear_tex_data(); 796 | } 797 | } 798 | --------------------------------------------------------------------------------