├── .cargo └── config.toml ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── publish.yaml ├── .gitignore ├── .mailmap ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── README.tpl ├── banner.png ├── examples ├── d3d12-buffer-winrs.rs ├── d3d12-buffer.rs ├── metal-buffer.rs └── vulkan-buffer.rs ├── release.toml ├── src ├── allocator │ ├── dedicated_block_allocator │ │ ├── mod.rs │ │ └── visualizer.rs │ ├── free_list_allocator │ │ ├── mod.rs │ │ └── visualizer.rs │ └── mod.rs ├── d3d12 │ ├── mod.rs │ └── visualizer.rs ├── lib.rs ├── metal │ ├── mod.rs │ └── visualizer.rs ├── result.rs ├── visualizer │ ├── allocation_reports.rs │ ├── memory_chunks.rs │ └── mod.rs └── vulkan │ ├── mod.rs │ └── visualizer.rs └── visualizer.png /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all())'] 2 | rustflags = [ 3 | # BEGIN - Embark standard lints v6 for Rust 1.55+ 4 | # do not change or add/remove here, but one can add exceptions after this section 5 | # for more info see: 6 | # "-Dunsafe_code", 7 | "-Wclippy::all", 8 | "-Wclippy::await_holding_lock", 9 | "-Wclippy::char_lit_as_u8", 10 | "-Wclippy::checked_conversions", 11 | "-Wclippy::dbg_macro", 12 | "-Wclippy::debug_assert_with_mut_call", 13 | "-Wclippy::doc_markdown", 14 | "-Wclippy::empty_enum", 15 | "-Wclippy::enum_glob_use", 16 | "-Wclippy::exit", 17 | "-Wclippy::expl_impl_clone_on_copy", 18 | "-Wclippy::explicit_deref_methods", 19 | "-Wclippy::explicit_into_iter_loop", 20 | "-Wclippy::fallible_impl_from", 21 | "-Wclippy::filter_map_next", 22 | "-Wclippy::flat_map_option", 23 | "-Wclippy::float_cmp_const", 24 | "-Wclippy::fn_params_excessive_bools", 25 | "-Wclippy::from_iter_instead_of_collect", 26 | "-Wclippy::if_let_mutex", 27 | "-Wclippy::implicit_clone", 28 | "-Wclippy::imprecise_flops", 29 | "-Wclippy::inefficient_to_string", 30 | "-Wclippy::invalid_upcast_comparisons", 31 | "-Wclippy::large_digit_groups", 32 | "-Wclippy::large_stack_arrays", 33 | "-Wclippy::large_types_passed_by_value", 34 | "-Wclippy::let_unit_value", 35 | "-Wclippy::linkedlist", 36 | "-Wclippy::lossy_float_literal", 37 | "-Wclippy::macro_use_imports", 38 | "-Wclippy::manual_ok_or", 39 | "-Wclippy::map_err_ignore", 40 | "-Wclippy::map_flatten", 41 | "-Wclippy::map_unwrap_or", 42 | "-Wclippy::match_on_vec_items", 43 | "-Wclippy::match_same_arms", 44 | "-Wclippy::match_wild_err_arm", 45 | "-Wclippy::match_wildcard_for_single_variants", 46 | "-Wclippy::mem_forget", 47 | "-Wclippy::missing_enforced_import_renames", 48 | "-Wclippy::mut_mut", 49 | "-Wclippy::mutex_integer", 50 | "-Wclippy::needless_borrow", 51 | "-Wclippy::needless_continue", 52 | "-Wclippy::needless_for_each", 53 | "-Wclippy::option_option", 54 | "-Wclippy::path_buf_push_overwrite", 55 | "-Wclippy::ptr_as_ptr", 56 | "-Wclippy::rc_mutex", 57 | "-Wclippy::ref_option_ref", 58 | "-Wclippy::rest_pat_in_fully_bound_structs", 59 | "-Wclippy::same_functions_in_if_condition", 60 | # "-Wclippy::semicolon_if_nothing_returned", 61 | # "-Wclippy::single_match_else", 62 | "-Wclippy::string_add_assign", 63 | "-Wclippy::string_add", 64 | "-Wclippy::string_lit_as_bytes", 65 | "-Wclippy::string_to_string", 66 | # "-Wclippy::todo", 67 | "-Wclippy::trait_duplication_in_bounds", 68 | "-Wclippy::unimplemented", 69 | "-Wclippy::unnested_or_patterns", 70 | "-Wclippy::unused_self", 71 | "-Wclippy::useless_transmute", 72 | "-Wclippy::verbose_file_reads", 73 | "-Wclippy::zero_sized_map_values", 74 | "-Wfuture_incompatible", 75 | "-Wnonstandard_style", 76 | "-Wrust_2018_idioms", 77 | # END - Embark standard lints v6 for Rust 1.55+ 78 | 79 | # Our additional lints 80 | "-Wclippy::cognitive_complexity", 81 | "-Wclippy::needless_pass_by_value", 82 | "-Wclippy::option_if_let_else", 83 | # "-Wclippy::print_stderr", 84 | # "-Wclippy::print_stdout", 85 | "-Wclippy::trivially_copy_pass_by_ref", 86 | "-Wclippy::use_self", 87 | "-Wclippy::useless_let_if_seq", 88 | 89 | # Default-allow warnings from `rustc -W help` 90 | "-Wdeprecated-in-future", 91 | "-Wtrivial-casts", 92 | "-Wtrivial-numeric-casts", 93 | # "-Wunused-crate-dependencies", 94 | "-Wunused-qualifications", 95 | ] 96 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | check_msrv: 7 | name: Check MSRV (1.71.0) 8 | strategy: 9 | matrix: 10 | include: 11 | - os: ubuntu-latest 12 | features: vulkan 13 | - os: windows-latest 14 | features: vulkan,d3d12 15 | - os: macos-latest 16 | features: vulkan,metal 17 | runs-on: ${{ matrix.os }} 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: dtolnay/rust-toolchain@nightly 21 | - name: Generate lockfile with minimal dependency versions 22 | run: cargo +nightly generate-lockfile -Zminimal-versions 23 | - uses: dtolnay/rust-toolchain@1.71.0 24 | # Note that examples are extempt from the MSRV check, so that they can use newer Rust features 25 | - run: cargo check --workspace --features ${{ matrix.features }} --no-default-features 26 | 27 | test: 28 | name: Test Suite 29 | strategy: 30 | matrix: 31 | include: 32 | - os: ubuntu-latest 33 | features: vulkan,visualizer 34 | - os: windows-latest 35 | features: vulkan,visualizer,d3d12,public-winapi 36 | - os: macos-latest 37 | features: vulkan,visualizer,metal 38 | runs-on: ${{ matrix.os }} 39 | steps: 40 | - uses: actions/checkout@v4 41 | - name: Cargo test all targets 42 | run: cargo test --workspace --all-targets --features ${{ matrix.features }} --no-default-features 43 | - name: Cargo test docs 44 | run: cargo test --workspace --doc --features ${{ matrix.features }} --no-default-features 45 | 46 | fmt: 47 | name: Rustfmt 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | - name: Cargo fmt 52 | run: cargo fmt --all -- --check 53 | 54 | clippy: 55 | name: Clippy 56 | strategy: 57 | matrix: 58 | include: 59 | - os: ubuntu-latest 60 | features: vulkan,visualizer 61 | - os: windows-latest 62 | features: vulkan,visualizer,d3d12,public-winapi 63 | - os: macos-latest 64 | features: vulkan,visualizer,metal 65 | runs-on: ${{ matrix.os }} 66 | steps: 67 | - uses: actions/checkout@v4 68 | - name: Cargo clippy 69 | run: cargo clippy --workspace --all-targets --features ${{ matrix.features }} --no-default-features -- -D warnings 70 | 71 | doc: 72 | name: Build documentation 73 | strategy: 74 | matrix: 75 | # Rely on Windows and Mac to also compile the Vulkan portion (via --all-features) 76 | os: [windows-latest, macos-latest] 77 | runs-on: ${{ matrix.os }} 78 | env: 79 | RUSTDOCFLAGS: -Dwarnings 80 | steps: 81 | - uses: actions/checkout@v4 82 | - name: Build documentation 83 | run: cargo doc --no-deps --workspace --all-features --document-private-items 84 | 85 | readme: 86 | runs-on: ubuntu-latest 87 | steps: 88 | - uses: actions/checkout@v4 89 | - name: Use cached cargo-readme 90 | uses: actions/cache@v4 91 | id: cargo-readme-cache 92 | with: 93 | path: ~/.cargo/bin/cargo-readme 94 | key: ${{ runner.os }}-cargo-readme 95 | - name: Install cargo-readme 96 | if: steps.cargo-readme-cache.outputs.cache-hit != 'true' 97 | run: cargo install cargo-readme 98 | - name: Check if README.md is up-to-date 99 | run: | 100 | cargo readme > README.md 101 | git diff --quiet || (echo '::error::Generated README is different, please regenerate with `cargo readme > README.md`'; git diff; false) 102 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | paths: "/Cargo.toml" 7 | 8 | jobs: 9 | Publish: 10 | if: github.repository_owner == 'Traverse-Research' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Publish 15 | run: cargo publish --token ${{ secrets.cratesio_token }} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | 17 | imgui.ini 18 | *.bak 19 | *.fmt 20 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Manon Oomen 2 | Manon Oomen <64775926+max-traverse@users.noreply.github.com> 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@traverseresearch.nl. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gpu-allocator" 3 | version = "0.27.0" 4 | authors = ["Traverse Research "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "Memory allocator for GPU memory in Vulkan and DirectX 12" 8 | categories = ["rendering", "rendering::graphics-api"] 9 | homepage = "https://github.com/Traverse-Research/gpu-allocator" 10 | repository = "https://github.com/Traverse-Research/gpu-allocator" 11 | keywords = ["vulkan", "memory", "allocator"] 12 | documentation = "https://docs.rs/gpu-allocator/" 13 | rust-version = "1.71" 14 | 15 | include = [ 16 | "/README.md", 17 | "/LICENSE-*", 18 | "/src", 19 | "/examples", 20 | ] 21 | 22 | [package.metadata.docs.rs] 23 | all-features = true 24 | 25 | [dependencies] 26 | log = "0.4" 27 | thiserror = "1.0" 28 | presser = { version = "0.3" } 29 | # Only needed for Vulkan. Disable all default features as good practice, 30 | # such as the ability to link/load a Vulkan library. 31 | ash = { version = "0.38", optional = true, default-features = false, features = ["debug"] } 32 | # Only needed for visualizer. 33 | egui = { version = ">=0.24, <=0.27", optional = true, default-features = false } 34 | egui_extras = { version = ">=0.24, <=0.27", optional = true, default-features = false } 35 | 36 | [target.'cfg(target_vendor = "apple")'.dependencies] 37 | objc2 = { version = "0.6", default-features = false, optional = true } 38 | objc2-foundation = { version = "0.3", default-features = false, optional = true } 39 | objc2-metal = { version = "0.3", default-features = false, features = [ 40 | "MTLAccelerationStructure", 41 | "MTLAllocation", 42 | "MTLBuffer", 43 | "MTLDevice", 44 | "MTLHeap", 45 | "MTLResource", 46 | "MTLTexture", 47 | "std", 48 | ], optional = true } 49 | 50 | [target.'cfg(windows)'.dependencies] 51 | # Only needed for public-winapi interop helpers 52 | winapi = { version = "0.3.9", features = ["d3d12", "winerror", "impl-default", "impl-debug"], optional = true } 53 | 54 | [target.'cfg(windows)'.dependencies.windows] 55 | version = ">=0.53, <=0.61" 56 | features = [ 57 | "Win32_Graphics_Direct3D12", 58 | "Win32_Graphics_Dxgi_Common", 59 | ] 60 | optional = true 61 | 62 | [dev-dependencies] 63 | # Enable the "loaded" feature to be able to access the Vulkan entrypoint. 64 | ash = { version = "0.38", default-features = false, features = ["debug", "loaded"] } 65 | env_logger = "0.10" 66 | 67 | [target.'cfg(windows)'.dev-dependencies] 68 | winapi = { version = "0.3.9", features = ["d3d12", "d3d12sdklayers", "dxgi1_6", "winerror", "impl-default", "impl-debug", "winuser", "windowsx", "libloaderapi"] } 69 | 70 | [target.'cfg(windows)'.dev-dependencies.windows] 71 | # API-breaks since Windows 0.58 only affect our examples 72 | version = ">=0.58, <=0.61" 73 | features = [ 74 | "Win32_Graphics_Direct3D", 75 | "Win32_Graphics_Direct3D12", 76 | "Win32_Graphics_Dxgi_Common", 77 | ] 78 | 79 | [target.'cfg(target_vendor = "apple")'.dev-dependencies] 80 | objc2-metal = { version = "0.3", default-features = false, features = [ 81 | "MTLPixelFormat", 82 | ] } 83 | 84 | [[example]] 85 | name = "vulkan-buffer" 86 | required-features = ["vulkan", "ash/loaded"] 87 | 88 | [[example]] 89 | name = "d3d12-buffer" 90 | required-features = ["d3d12", "public-winapi"] 91 | 92 | [[example]] 93 | name = "d3d12-buffer-winrs" 94 | required-features = ["d3d12"] 95 | 96 | [[example]] 97 | name = "metal-buffer" 98 | required-features = ["metal"] 99 | 100 | [features] 101 | visualizer = ["dep:egui", "dep:egui_extras"] 102 | vulkan = ["dep:ash"] 103 | d3d12 = ["dep:windows"] 104 | metal = ["dep:objc2", "dep:objc2-metal", "dep:objc2-foundation"] 105 | # Expose helper functionality for winapi types to interface with gpu-allocator, which is primarily windows-rs driven 106 | public-winapi = ["dep:winapi"] 107 | 108 | default = ["d3d12", "vulkan", "metal"] 109 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Traverse Research B.V. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Traverse Research B.V. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📒 gpu-allocator 2 | 3 | [![Actions Status](https://img.shields.io/github/actions/workflow/status/Traverse-Research/gpu-allocator/ci.yml?branch=main&logo=github)](https://github.com/Traverse-Research/gpu-allocator/actions) 4 | [![Latest version](https://img.shields.io/crates/v/gpu-allocator.svg?logo=rust)](https://crates.io/crates/gpu-allocator) 5 | [![Docs](https://img.shields.io/docsrs/gpu-allocator?logo=docs.rs)](https://docs.rs/gpu-allocator/) 6 | [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE-MIT) 7 | [![LICENSE](https://img.shields.io/badge/license-apache-blue.svg?logo=apache)](LICENSE-APACHE) 8 | [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-v1.4%20adopted-ff69b4.svg)](../main/CODE_OF_CONDUCT.md) 9 | [![MSRV](https://img.shields.io/badge/rustc-1.71.0+-ab6000.svg)](https://blog.rust-lang.org/2023/07/13/Rust-1.71.0.html) 10 | 11 | [![Banner](banner.png)](https://traverseresearch.nl) 12 | 13 | ```toml 14 | [dependencies] 15 | gpu-allocator = "0.27.0" 16 | ``` 17 | 18 | ![Visualizer](visualizer.png) 19 | 20 | This crate provides a fully written in Rust memory allocator for Vulkan, DirectX 12 and Metal. 21 | 22 | ## [Windows-rs] and [winapi] 23 | 24 | `gpu-allocator` recently migrated from [winapi] to [windows-rs] but still provides convenient helpers to convert to and from [winapi] types, enabled when compiling with the `public-winapi` crate feature. 25 | 26 | [Windows-rs]: https://github.com/microsoft/windows-rs 27 | [winapi]: https://github.com/retep998/winapi-rs 28 | 29 | ## Setting up the Vulkan memory allocator 30 | 31 | ```rust 32 | use gpu_allocator::vulkan::*; 33 | 34 | let mut allocator = Allocator::new(&AllocatorCreateDesc { 35 | instance, 36 | device, 37 | physical_device, 38 | debug_settings: Default::default(), 39 | buffer_device_address: true, // Ideally, check the BufferDeviceAddressFeatures struct. 40 | allocation_sizes: Default::default(), 41 | }); 42 | ``` 43 | 44 | ## Simple Vulkan allocation example 45 | 46 | ```rust 47 | use gpu_allocator::vulkan::*; 48 | use gpu_allocator::MemoryLocation; 49 | 50 | // Setup vulkan info 51 | let vk_info = vk::BufferCreateInfo::default() 52 | .size(512) 53 | .usage(vk::BufferUsageFlags::STORAGE_BUFFER); 54 | 55 | let buffer = unsafe { device.create_buffer(&vk_info, None) }.unwrap(); 56 | let requirements = unsafe { device.get_buffer_memory_requirements(buffer) }; 57 | 58 | let allocation = allocator 59 | .allocate(&AllocationCreateDesc { 60 | name: "Example allocation", 61 | requirements, 62 | location: MemoryLocation::CpuToGpu, 63 | linear: true, // Buffers are always linear 64 | allocation_scheme: AllocationScheme::GpuAllocatorManaged, 65 | }).unwrap(); 66 | 67 | // Bind memory to the buffer 68 | unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()).unwrap() }; 69 | 70 | // Cleanup 71 | allocator.free(allocation).unwrap(); 72 | unsafe { device.destroy_buffer(buffer, None) }; 73 | ``` 74 | 75 | ## Setting up the D3D12 memory allocator 76 | 77 | ```rust 78 | use gpu_allocator::d3d12::*; 79 | 80 | let mut allocator = Allocator::new(&AllocatorCreateDesc { 81 | device: ID3D12DeviceVersion::Device(device), 82 | debug_settings: Default::default(), 83 | allocation_sizes: Default::default(), 84 | }); 85 | ``` 86 | 87 | ## Simple d3d12 allocation example 88 | 89 | ```rust 90 | use gpu_allocator::d3d12::*; 91 | use gpu_allocator::MemoryLocation; 92 | 93 | 94 | let buffer_desc = Direct3D12::D3D12_RESOURCE_DESC { 95 | Dimension: Direct3D12::D3D12_RESOURCE_DIMENSION_BUFFER, 96 | Alignment: 0, 97 | Width: 512, 98 | Height: 1, 99 | DepthOrArraySize: 1, 100 | MipLevels: 1, 101 | Format: Dxgi::Common::DXGI_FORMAT_UNKNOWN, 102 | SampleDesc: Dxgi::Common::DXGI_SAMPLE_DESC { 103 | Count: 1, 104 | Quality: 0, 105 | }, 106 | Layout: Direct3D12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 107 | Flags: Direct3D12::D3D12_RESOURCE_FLAG_NONE, 108 | }; 109 | let allocation_desc = AllocationCreateDesc::from_d3d12_resource_desc( 110 | &allocator.device(), 111 | &buffer_desc, 112 | "Example allocation", 113 | MemoryLocation::GpuOnly, 114 | ); 115 | let allocation = allocator.allocate(&allocation_desc).unwrap(); 116 | let mut resource: Option = None; 117 | let hr = unsafe { 118 | device.CreatePlacedResource( 119 | allocation.heap(), 120 | allocation.offset(), 121 | &buffer_desc, 122 | Direct3D12::D3D12_RESOURCE_STATE_COMMON, 123 | None, 124 | &mut resource, 125 | ) 126 | }?; 127 | 128 | // Cleanup 129 | drop(resource); 130 | allocator.free(allocation).unwrap(); 131 | ``` 132 | 133 | ## Setting up the Metal memory allocator 134 | 135 | ```rust 136 | use gpu_allocator::metal::*; 137 | let mut allocator = Allocator::new(&AllocatorCreateDesc { 138 | device: device.clone(), 139 | debug_settings: Default::default(), 140 | allocation_sizes: Default::default(), 141 | }); 142 | ``` 143 | 144 | ## Simple Metal allocation example 145 | ```rust 146 | use gpu_allocator::metal::*; 147 | use gpu_allocator::MemoryLocation; 148 | let allocation_desc = AllocationCreateDesc::buffer( 149 | &device, 150 | "Example allocation", 151 | 512, // size in bytes 152 | MemoryLocation::GpuOnly, 153 | ); 154 | let allocation = allocator.allocate(&allocation_desc).unwrap(); 155 | let heap = unsafe { allocation.heap() }; 156 | let resource = unsafe { 157 | heap.newBufferWithLength_options_offset( 158 | allocation.size() as usize, 159 | heap.resourceOptions(), 160 | allocation.offset() as usize, 161 | ) 162 | } 163 | .unwrap(); 164 | 165 | // Cleanup 166 | drop(resource); 167 | allocator.free(&allocation).unwrap(); 168 | ``` 169 | 170 | ## Minimum Supported Rust Version 171 | 172 | The MSRV for this crate and the `vulkan`, `d3d12` and `metal` features is Rust 1.71. Any other features such as the `visualizer` (with all the `egui` dependencies) may have a higher requirement and are not tested in our CI. 173 | 174 | ## License 175 | 176 | Licensed under either of 177 | 178 | - Apache License, Version 2.0, ([LICENSE-APACHE](../master/LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 179 | - MIT license ([LICENSE-MIT](../master/LICENSE-MIT) or http://opensource.org/licenses/MIT) 180 | 181 | at your option. 182 | 183 | ## Alternative libraries 184 | 185 | - [vk-mem-rs](https://github.com/gwihlidal/vk-mem-rs) 186 | 187 | ## Contribution 188 | 189 | Unless you explicitly state otherwise, any contribution intentionally 190 | submitted for inclusion in the work by you, as defined in the Apache-2.0 191 | license, shall be dual licensed as above, without any additional terms or 192 | conditions. 193 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # 📒 gpu-allocator 2 | 3 | [![Actions Status](https://img.shields.io/github/actions/workflow/status/Traverse-Research/gpu-allocator/ci.yml?branch=main&logo=github)](https://github.com/Traverse-Research/gpu-allocator/actions) 4 | [![Latest version](https://img.shields.io/crates/v/gpu-allocator.svg?logo=rust)](https://crates.io/crates/gpu-allocator) 5 | [![Docs](https://img.shields.io/docsrs/gpu-allocator?logo=docs.rs)](https://docs.rs/gpu-allocator/) 6 | [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE-MIT) 7 | [![LICENSE](https://img.shields.io/badge/license-apache-blue.svg?logo=apache)](LICENSE-APACHE) 8 | [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-v1.4%20adopted-ff69b4.svg)](../main/CODE_OF_CONDUCT.md) 9 | [![MSRV](https://img.shields.io/badge/rustc-1.71.0+-ab6000.svg)](https://blog.rust-lang.org/2023/07/13/Rust-1.71.0.html) 10 | 11 | [![Banner](banner.png)](https://traverseresearch.nl) 12 | 13 | ```toml 14 | [dependencies] 15 | gpu-allocator = "0.27.0" 16 | ``` 17 | 18 | ![Visualizer](visualizer.png) 19 | 20 | {{readme}} 21 | 22 | ## Minimum Supported Rust Version 23 | 24 | The MSRV for this crate and the `vulkan`, `d3d12` and `metal` features is Rust 1.71. Any other features such as the `visualizer` (with all the `egui` dependencies) may have a higher requirement and are not tested in our CI. 25 | 26 | ## License 27 | 28 | Licensed under either of 29 | 30 | - Apache License, Version 2.0, ([LICENSE-APACHE](../master/LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 31 | - MIT license ([LICENSE-MIT](../master/LICENSE-MIT) or http://opensource.org/licenses/MIT) 32 | 33 | at your option. 34 | 35 | ## Alternative libraries 36 | 37 | - [vk-mem-rs](https://github.com/gwihlidal/vk-mem-rs) 38 | 39 | ## Contribution 40 | 41 | Unless you explicitly state otherwise, any contribution intentionally 42 | submitted for inclusion in the work by you, as defined in the Apache-2.0 43 | license, shall be dual licensed as above, without any additional terms or 44 | conditions. 45 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Traverse-Research/gpu-allocator/507451d387c8f281f91efdd53b5d8a6334213dfb/banner.png -------------------------------------------------------------------------------- /examples/d3d12-buffer-winrs.rs: -------------------------------------------------------------------------------- 1 | //! Example showcasing [`gpu-allocator`] with types and functions from the [`windows`] crate. 2 | use gpu_allocator::{ 3 | d3d12::{ 4 | AllocationCreateDesc, Allocator, AllocatorCreateDesc, ID3D12DeviceVersion, ResourceCategory, 5 | }, 6 | MemoryLocation, 7 | }; 8 | use log::*; 9 | use windows::{ 10 | core::{Interface, Result}, 11 | Win32::{ 12 | Foundation::E_NOINTERFACE, 13 | Graphics::{ 14 | Direct3D::{D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_12_0}, 15 | Direct3D12::{ 16 | D3D12CreateDevice, ID3D12Device, ID3D12Resource, 17 | D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT, D3D12_RESOURCE_DESC, 18 | D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_FLAG_NONE, 19 | D3D12_RESOURCE_STATE_COMMON, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 20 | }, 21 | Dxgi::{ 22 | Common::{DXGI_FORMAT_UNKNOWN, DXGI_SAMPLE_DESC}, 23 | CreateDXGIFactory2, IDXGIAdapter4, IDXGIFactory6, DXGI_ADAPTER_FLAG3_SOFTWARE, 24 | DXGI_ERROR_NOT_FOUND, 25 | }, 26 | }, 27 | }, 28 | }; 29 | 30 | fn create_d3d12_device(dxgi_factory: &IDXGIFactory6) -> Option { 31 | for idx in 0.. { 32 | // TODO: Might as well return Result<> from this function 33 | let adapter1 = match unsafe { dxgi_factory.EnumAdapters1(idx) } { 34 | Ok(a) => a, 35 | Err(e) if e.code() == DXGI_ERROR_NOT_FOUND => break, 36 | Err(e) => panic!("{:?}", e), 37 | }; 38 | let adapter4: IDXGIAdapter4 = adapter1.cast().unwrap(); 39 | 40 | let desc = unsafe { adapter4.GetDesc3() }.unwrap(); 41 | // Skip software adapters 42 | // Vote for https://github.com/microsoft/windows-rs/issues/793! 43 | if (desc.Flags & DXGI_ADAPTER_FLAG3_SOFTWARE) == DXGI_ADAPTER_FLAG3_SOFTWARE { 44 | continue; 45 | } 46 | 47 | let feature_levels = [ 48 | (D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"), 49 | (D3D_FEATURE_LEVEL_11_1, "D3D_FEATURE_LEVEL_11_1"), 50 | (D3D_FEATURE_LEVEL_12_0, "D3D_FEATURE_LEVEL_12_0"), 51 | ]; 52 | 53 | let device = 54 | feature_levels 55 | .iter() 56 | .rev() 57 | .find_map(|&(feature_level, feature_level_name)| { 58 | let mut device = None; 59 | match unsafe { D3D12CreateDevice(&adapter4, feature_level, &mut device) } { 60 | Ok(()) => { 61 | info!("Using D3D12 feature level: {}", feature_level_name); 62 | Some(device.unwrap()) 63 | } 64 | Err(e) if e.code() == E_NOINTERFACE => { 65 | error!("ID3D12Device interface not supported"); 66 | None 67 | } 68 | Err(e) => { 69 | info!( 70 | "D3D12 feature level {} not supported: {}", 71 | feature_level_name, e 72 | ); 73 | None 74 | } 75 | } 76 | }); 77 | if device.is_some() { 78 | return device; 79 | } 80 | } 81 | 82 | None 83 | } 84 | 85 | fn main() -> Result<()> { 86 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); 87 | 88 | let dxgi_factory = unsafe { 89 | CreateDXGIFactory2(windows::Win32::Graphics::Dxgi::DXGI_CREATE_FACTORY_FLAGS::default()) 90 | }?; 91 | 92 | let device = create_d3d12_device(&dxgi_factory).expect("Failed to create D3D12 device."); 93 | 94 | // Setting up the allocator 95 | let mut allocator = Allocator::new(&AllocatorCreateDesc { 96 | device: ID3D12DeviceVersion::Device(device.clone()), 97 | debug_settings: Default::default(), 98 | allocation_sizes: Default::default(), 99 | }) 100 | .unwrap(); 101 | 102 | // Test allocating Gpu Only memory 103 | { 104 | let test_buffer_desc = D3D12_RESOURCE_DESC { 105 | Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, 106 | Alignment: D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64, 107 | Width: 512, 108 | Height: 1, 109 | DepthOrArraySize: 1, 110 | MipLevels: 1, 111 | Format: DXGI_FORMAT_UNKNOWN, 112 | SampleDesc: DXGI_SAMPLE_DESC { 113 | Count: 1, 114 | Quality: 0, 115 | }, 116 | Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 117 | Flags: D3D12_RESOURCE_FLAG_NONE, 118 | }; 119 | 120 | let allocation_desc = AllocationCreateDesc::from_d3d12_resource_desc( 121 | allocator.device(), 122 | &test_buffer_desc, 123 | "Test allocation (Gpu only)", 124 | MemoryLocation::GpuOnly, 125 | ); 126 | let allocation = allocator.allocate(&allocation_desc).unwrap(); 127 | 128 | let mut resource: Option = None; 129 | unsafe { 130 | device.CreatePlacedResource( 131 | allocation.heap(), 132 | allocation.offset(), 133 | &test_buffer_desc, 134 | D3D12_RESOURCE_STATE_COMMON, 135 | None, 136 | &mut resource, 137 | ) 138 | }?; 139 | 140 | drop(resource); 141 | 142 | allocator.free(allocation).unwrap(); 143 | info!("Allocation and deallocation of GpuOnly memory was successful."); 144 | } 145 | 146 | // Test allocating Cpu to Gpu memory 147 | { 148 | let test_buffer_desc = D3D12_RESOURCE_DESC { 149 | Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, 150 | Alignment: D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64, 151 | Width: 512, 152 | Height: 1, 153 | DepthOrArraySize: 1, 154 | MipLevels: 1, 155 | Format: DXGI_FORMAT_UNKNOWN, 156 | SampleDesc: DXGI_SAMPLE_DESC { 157 | Count: 1, 158 | Quality: 0, 159 | }, 160 | Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 161 | Flags: D3D12_RESOURCE_FLAG_NONE, 162 | }; 163 | 164 | let alloc_info = unsafe { device.GetResourceAllocationInfo(0, &[test_buffer_desc]) }; 165 | 166 | let allocation = allocator 167 | .allocate(&AllocationCreateDesc { 168 | name: "Test allocation (Cpu To Gpu)", 169 | location: MemoryLocation::CpuToGpu, 170 | size: alloc_info.SizeInBytes, 171 | alignment: alloc_info.Alignment, 172 | resource_category: ResourceCategory::Buffer, 173 | }) 174 | .unwrap(); 175 | 176 | let mut resource: Option = None; 177 | unsafe { 178 | device.CreatePlacedResource( 179 | allocation.heap(), 180 | allocation.offset(), 181 | &test_buffer_desc, 182 | D3D12_RESOURCE_STATE_COMMON, 183 | None, 184 | &mut resource, 185 | ) 186 | }?; 187 | 188 | drop(resource); 189 | 190 | allocator.free(allocation).unwrap(); 191 | info!("Allocation and deallocation of CpuToGpu memory was successful."); 192 | } 193 | 194 | // Test allocating Gpu to Cpu memory 195 | { 196 | let test_buffer_desc = D3D12_RESOURCE_DESC { 197 | Dimension: D3D12_RESOURCE_DIMENSION_BUFFER, 198 | Alignment: D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64, 199 | Width: 512, 200 | Height: 1, 201 | DepthOrArraySize: 1, 202 | MipLevels: 1, 203 | Format: DXGI_FORMAT_UNKNOWN, 204 | SampleDesc: DXGI_SAMPLE_DESC { 205 | Count: 1, 206 | Quality: 0, 207 | }, 208 | Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 209 | Flags: D3D12_RESOURCE_FLAG_NONE, 210 | }; 211 | 212 | let alloc_info = unsafe { device.GetResourceAllocationInfo(0, &[test_buffer_desc]) }; 213 | 214 | let allocation = allocator 215 | .allocate(&AllocationCreateDesc { 216 | name: "Test allocation (Gpu to Cpu)", 217 | location: MemoryLocation::GpuToCpu, 218 | size: alloc_info.SizeInBytes, 219 | alignment: alloc_info.Alignment, 220 | resource_category: ResourceCategory::Buffer, 221 | }) 222 | .unwrap(); 223 | 224 | let mut resource: Option = None; 225 | unsafe { 226 | device.CreatePlacedResource( 227 | allocation.heap(), 228 | allocation.offset(), 229 | &test_buffer_desc, 230 | D3D12_RESOURCE_STATE_COMMON, 231 | None, 232 | &mut resource, 233 | ) 234 | }?; 235 | 236 | drop(resource); 237 | 238 | allocator.free(allocation).unwrap(); 239 | info!("Allocation and deallocation of CpuToGpu memory was successful."); 240 | } 241 | 242 | Ok(()) 243 | } 244 | -------------------------------------------------------------------------------- /examples/d3d12-buffer.rs: -------------------------------------------------------------------------------- 1 | //! Example showcasing [`winapi`] interop with [`gpu-allocator`] which is driven by the [`windows`] crate. 2 | use winapi::{ 3 | shared::{dxgiformat, winerror}, 4 | um::{d3d12, d3dcommon}, 5 | Interface, 6 | }; 7 | 8 | mod all_dxgi { 9 | pub use winapi::shared::{dxgi1_3::*, dxgi1_6::*, dxgitype::*}; 10 | } 11 | 12 | use gpu_allocator::{ 13 | d3d12::{ 14 | AllocationCreateDesc, Allocator, AllocatorCreateDesc, ID3D12DeviceVersion, 15 | ResourceCategory, ToWinapi, ToWindows, 16 | }, 17 | MemoryLocation, 18 | }; 19 | use log::*; 20 | 21 | fn create_d3d12_device( 22 | dxgi_factory: *mut all_dxgi::IDXGIFactory6, 23 | ) -> Option<*mut d3d12::ID3D12Device> { 24 | for idx in 0.. { 25 | let mut adapter4: *mut all_dxgi::IDXGIAdapter4 = std::ptr::null_mut(); 26 | let hr = unsafe { 27 | dxgi_factory.as_ref().unwrap().EnumAdapters1( 28 | idx, 29 | <*mut *mut all_dxgi::IDXGIAdapter4>::cast(&mut adapter4), 30 | ) 31 | }; 32 | 33 | if hr == winerror::DXGI_ERROR_NOT_FOUND { 34 | break; 35 | } 36 | 37 | assert_eq!(hr, winerror::S_OK); 38 | 39 | let mut desc = all_dxgi::DXGI_ADAPTER_DESC3::default(); 40 | let hr = unsafe { adapter4.as_ref().unwrap().GetDesc3(&mut desc) }; 41 | if hr != winerror::S_OK { 42 | error!("Failed to get adapter description for adapter"); 43 | continue; 44 | } 45 | 46 | // Skip software adapters 47 | if (desc.Flags & all_dxgi::DXGI_ADAPTER_FLAG3_SOFTWARE) 48 | == all_dxgi::DXGI_ADAPTER_FLAG3_SOFTWARE 49 | { 50 | continue; 51 | } 52 | 53 | let feature_levels = [ 54 | (d3dcommon::D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"), 55 | (d3dcommon::D3D_FEATURE_LEVEL_11_1, "D3D_FEATURE_LEVEL_11_1"), 56 | (d3dcommon::D3D_FEATURE_LEVEL_12_0, "D3D_FEATURE_LEVEL_12_0"), 57 | ]; 58 | 59 | let device = 60 | feature_levels 61 | .iter() 62 | .rev() 63 | .find_map(|&(feature_level, feature_level_name)| { 64 | let mut device: *mut d3d12::ID3D12Device = std::ptr::null_mut(); 65 | let hr = unsafe { 66 | d3d12::D3D12CreateDevice( 67 | adapter4.cast(), 68 | feature_level, 69 | &d3d12::ID3D12Device::uuidof(), 70 | <*mut *mut d3d12::ID3D12Device>::cast(&mut device), 71 | ) 72 | }; 73 | match hr { 74 | winerror::S_OK => { 75 | info!("Using D3D12 feature level: {}.", feature_level_name); 76 | Some(device) 77 | } 78 | winerror::E_NOINTERFACE => { 79 | error!("ID3D12Device interface not supported."); 80 | None 81 | } 82 | _ => { 83 | info!( 84 | "D3D12 feature level: {} not supported: {:x}", 85 | feature_level_name, hr 86 | ); 87 | None 88 | } 89 | } 90 | }); 91 | if device.is_some() { 92 | return device; 93 | } 94 | } 95 | 96 | None 97 | } 98 | 99 | fn main() { 100 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); 101 | 102 | let dxgi_factory = { 103 | let mut dxgi_factory: *mut all_dxgi::IDXGIFactory6 = std::ptr::null_mut(); 104 | let hr = unsafe { 105 | all_dxgi::CreateDXGIFactory2( 106 | 0, 107 | &all_dxgi::IID_IDXGIFactory6, 108 | <*mut *mut all_dxgi::IDXGIFactory6>::cast(&mut dxgi_factory), 109 | ) 110 | }; 111 | 112 | assert_eq!(hr, winerror::S_OK, "Failed to create DXGI factory"); 113 | dxgi_factory 114 | }; 115 | 116 | let device = create_d3d12_device(dxgi_factory).expect("Failed to create D3D12 device."); 117 | 118 | // Setting up the allocator 119 | let mut allocator = Allocator::new(&AllocatorCreateDesc { 120 | device: ID3D12DeviceVersion::Device(device.as_windows().clone()), 121 | debug_settings: Default::default(), 122 | allocation_sizes: Default::default(), 123 | }) 124 | .unwrap(); 125 | 126 | let device = unsafe { device.as_ref() }.unwrap(); 127 | 128 | // Test allocating Gpu Only memory 129 | { 130 | let test_buffer_desc = d3d12::D3D12_RESOURCE_DESC { 131 | Dimension: d3d12::D3D12_RESOURCE_DIMENSION_BUFFER, 132 | Alignment: d3d12::D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64, 133 | Width: 512, 134 | Height: 1, 135 | DepthOrArraySize: 1, 136 | MipLevels: 1, 137 | Format: dxgiformat::DXGI_FORMAT_UNKNOWN, 138 | SampleDesc: all_dxgi::DXGI_SAMPLE_DESC { 139 | Count: 1, 140 | Quality: 0, 141 | }, 142 | Layout: d3d12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 143 | Flags: d3d12::D3D12_RESOURCE_FLAG_NONE, 144 | }; 145 | 146 | let allocation_desc = AllocationCreateDesc::from_winapi_d3d12_resource_desc( 147 | device, 148 | &test_buffer_desc, 149 | "Test allocation (Gpu Only)", 150 | MemoryLocation::GpuOnly, 151 | ); 152 | let allocation = allocator.allocate(&allocation_desc).unwrap(); 153 | 154 | let mut resource: *mut d3d12::ID3D12Resource = std::ptr::null_mut(); 155 | let hr = unsafe { 156 | device.CreatePlacedResource( 157 | allocation.heap().as_winapi() as *mut _, 158 | allocation.offset(), 159 | &test_buffer_desc, 160 | d3d12::D3D12_RESOURCE_STATE_COMMON, 161 | std::ptr::null(), 162 | &d3d12::IID_ID3D12Resource, 163 | <*mut *mut d3d12::ID3D12Resource>::cast(&mut resource), 164 | ) 165 | }; 166 | if hr != winerror::S_OK { 167 | panic!("Failed to create placed resource."); 168 | } 169 | 170 | unsafe { resource.as_ref().unwrap().Release() }; 171 | 172 | allocator.free(allocation).unwrap(); 173 | info!("Allocation and deallocation of GpuOnly memory was successful."); 174 | } 175 | 176 | // Test allocating Cpu to Gpu memory 177 | { 178 | let test_buffer_desc = d3d12::D3D12_RESOURCE_DESC { 179 | Dimension: d3d12::D3D12_RESOURCE_DIMENSION_BUFFER, 180 | Alignment: d3d12::D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64, 181 | Width: 512, 182 | Height: 1, 183 | DepthOrArraySize: 1, 184 | MipLevels: 1, 185 | Format: dxgiformat::DXGI_FORMAT_UNKNOWN, 186 | SampleDesc: all_dxgi::DXGI_SAMPLE_DESC { 187 | Count: 1, 188 | Quality: 0, 189 | }, 190 | Layout: d3d12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 191 | Flags: d3d12::D3D12_RESOURCE_FLAG_NONE, 192 | }; 193 | 194 | let alloc_info = unsafe { device.GetResourceAllocationInfo(0, 1, &test_buffer_desc) }; 195 | 196 | let allocation = allocator 197 | .allocate(&AllocationCreateDesc { 198 | name: "Test allocation (Cpu to Gpu)", 199 | location: MemoryLocation::CpuToGpu, 200 | size: alloc_info.SizeInBytes, 201 | alignment: alloc_info.Alignment, 202 | resource_category: ResourceCategory::Buffer, 203 | }) 204 | .unwrap(); 205 | 206 | let mut resource: *mut d3d12::ID3D12Resource = std::ptr::null_mut(); 207 | let hr = unsafe { 208 | device.CreatePlacedResource( 209 | allocation.heap().as_winapi() as *mut _, 210 | allocation.offset(), 211 | &test_buffer_desc, 212 | d3d12::D3D12_RESOURCE_STATE_COMMON, 213 | std::ptr::null(), 214 | &d3d12::IID_ID3D12Resource, 215 | <*mut *mut d3d12::ID3D12Resource>::cast(&mut resource), 216 | ) 217 | }; 218 | if hr != winerror::S_OK { 219 | panic!("Failed to create placed resource."); 220 | } 221 | 222 | unsafe { resource.as_ref().unwrap().Release() }; 223 | 224 | allocator.free(allocation).unwrap(); 225 | info!("Allocation and deallocation of CpuToGpu memory was successful."); 226 | } 227 | 228 | // Test allocating Gpu to Cpu memory 229 | { 230 | let test_buffer_desc = d3d12::D3D12_RESOURCE_DESC { 231 | Dimension: d3d12::D3D12_RESOURCE_DIMENSION_BUFFER, 232 | Alignment: d3d12::D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64, 233 | Width: 512, 234 | Height: 1, 235 | DepthOrArraySize: 1, 236 | MipLevels: 1, 237 | Format: dxgiformat::DXGI_FORMAT_UNKNOWN, 238 | SampleDesc: all_dxgi::DXGI_SAMPLE_DESC { 239 | Count: 1, 240 | Quality: 0, 241 | }, 242 | Layout: d3d12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 243 | Flags: d3d12::D3D12_RESOURCE_FLAG_NONE, 244 | }; 245 | 246 | let alloc_info = unsafe { device.GetResourceAllocationInfo(0, 1, &test_buffer_desc) }; 247 | 248 | let allocation = allocator 249 | .allocate(&AllocationCreateDesc { 250 | name: "Test allocation (Gpu to Cpu)", 251 | location: MemoryLocation::GpuToCpu, 252 | size: alloc_info.SizeInBytes, 253 | alignment: alloc_info.Alignment, 254 | resource_category: ResourceCategory::Buffer, 255 | }) 256 | .unwrap(); 257 | 258 | let mut resource: *mut d3d12::ID3D12Resource = std::ptr::null_mut(); 259 | let hr = unsafe { 260 | device.CreatePlacedResource( 261 | allocation.heap().as_winapi() as *mut _, 262 | allocation.offset(), 263 | &test_buffer_desc, 264 | d3d12::D3D12_RESOURCE_STATE_COMMON, 265 | std::ptr::null(), 266 | &d3d12::IID_ID3D12Resource, 267 | <*mut *mut d3d12::ID3D12Resource>::cast(&mut resource), 268 | ) 269 | }; 270 | if hr != winerror::S_OK { 271 | panic!("Failed to create placed resource."); 272 | } 273 | 274 | unsafe { resource.as_ref().unwrap().Release() }; 275 | 276 | allocator.free(allocation).unwrap(); 277 | info!("Allocation and deallocation of CpuToGpu memory was successful."); 278 | } 279 | 280 | drop(allocator); // Explicitly drop before destruction of device. 281 | unsafe { device.Release() }; 282 | unsafe { dxgi_factory.as_ref().unwrap().Release() }; 283 | } 284 | -------------------------------------------------------------------------------- /examples/metal-buffer.rs: -------------------------------------------------------------------------------- 1 | use gpu_allocator::metal::{AllocationCreateDesc, Allocator, AllocatorCreateDesc}; 2 | use log::info; 3 | use objc2_foundation::NSArray; 4 | use objc2_metal::{ 5 | MTLCreateSystemDefaultDevice, MTLDevice as _, MTLHeap, MTLPixelFormat, 6 | MTLPrimitiveAccelerationStructureDescriptor, MTLStorageMode, MTLTextureDescriptor, 7 | }; 8 | 9 | fn main() { 10 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); 11 | 12 | // Allow the innards of objc2-metal to link the static function below: 13 | // https://docs.rs/objc2-metal/0.2.2/objc2_metal/index.html 14 | #[link(name = "CoreGraphics", kind = "framework")] 15 | extern "C" {} 16 | 17 | let device = MTLCreateSystemDefaultDevice().expect("No MTLDevice found"); 18 | 19 | // Setting up the allocator 20 | let mut allocator = Allocator::new(&AllocatorCreateDesc { 21 | device: device.clone(), 22 | debug_settings: Default::default(), 23 | allocation_sizes: Default::default(), 24 | }) 25 | .unwrap(); 26 | 27 | // Test allocating Gpu Only memory 28 | { 29 | let allocation_desc = AllocationCreateDesc::buffer( 30 | &device, 31 | "Test allocation (Gpu Only)", 32 | 512, 33 | gpu_allocator::MemoryLocation::GpuOnly, 34 | ); 35 | let allocation = allocator.allocate(&allocation_desc).unwrap(); 36 | // SAFETY: We will only allocate objects on this heap within the returned offset and size 37 | let heap = unsafe { allocation.heap() }; 38 | let buffer = unsafe { 39 | heap.newBufferWithLength_options_offset( 40 | allocation.size() as usize, 41 | heap.resourceOptions(), 42 | allocation.offset() as usize, 43 | ) 44 | } 45 | .unwrap(); 46 | drop(buffer); 47 | allocator.free(&allocation).unwrap(); 48 | info!("Allocation and deallocation of GpuOnly memory was successful."); 49 | } 50 | 51 | // Test allocating Cpu to Gpu memory 52 | { 53 | let allocation_desc = AllocationCreateDesc::buffer( 54 | &device, 55 | "Test allocation (Cpu to Gpu)", 56 | 512, 57 | gpu_allocator::MemoryLocation::CpuToGpu, 58 | ); 59 | let allocation = allocator.allocate(&allocation_desc).unwrap(); 60 | // SAFETY: We will only allocate objects on this heap within the returned offset and size 61 | let heap = unsafe { allocation.heap() }; 62 | let buffer = unsafe { 63 | heap.newBufferWithLength_options_offset( 64 | allocation.size() as usize, 65 | heap.resourceOptions(), 66 | allocation.offset() as usize, 67 | ) 68 | } 69 | .unwrap(); 70 | drop(buffer); 71 | allocator.free(&allocation).unwrap(); 72 | info!("Allocation and deallocation of CpuToGpu memory was successful."); 73 | } 74 | 75 | // Test allocating Gpu to Cpu memory 76 | { 77 | let allocation_desc = AllocationCreateDesc::buffer( 78 | &device, 79 | "Test allocation (Gpu to Cpu)", 80 | 512, 81 | gpu_allocator::MemoryLocation::GpuToCpu, 82 | ); 83 | let allocation = allocator.allocate(&allocation_desc).unwrap(); 84 | // SAFETY: We will only allocate objects on this heap within the returned offset and size 85 | let heap = unsafe { allocation.heap() }; 86 | let buffer = unsafe { 87 | heap.newBufferWithLength_options_offset( 88 | allocation.size() as usize, 89 | heap.resourceOptions(), 90 | allocation.offset() as usize, 91 | ) 92 | } 93 | .unwrap(); 94 | drop(buffer); 95 | allocator.free(&allocation).unwrap(); 96 | info!("Allocation and deallocation of GpuToCpu memory was successful."); 97 | } 98 | 99 | // Test allocating texture 100 | { 101 | let texture_desc = unsafe { MTLTextureDescriptor::new() }; 102 | texture_desc.setPixelFormat(MTLPixelFormat::RGBA8Unorm); 103 | unsafe { texture_desc.setWidth(64) }; 104 | unsafe { texture_desc.setHeight(64) }; 105 | texture_desc.setStorageMode(MTLStorageMode::Private); 106 | let allocation_desc = 107 | AllocationCreateDesc::texture(&device, "Test allocation (Texture)", &texture_desc); 108 | let allocation = allocator.allocate(&allocation_desc).unwrap(); 109 | // SAFETY: We will only allocate objects on this heap within the returned offset and size 110 | let heap = unsafe { allocation.heap() }; 111 | let buffer = unsafe { 112 | heap.newTextureWithDescriptor_offset(&texture_desc, allocation.offset() as usize) 113 | } 114 | .unwrap(); 115 | drop(buffer); 116 | allocator.free(&allocation).unwrap(); 117 | info!("Allocation and deallocation of Texture was successful."); 118 | } 119 | 120 | // Test allocating acceleration structure 121 | { 122 | let empty_array = NSArray::from_slice(&[]); 123 | let acc_desc = MTLPrimitiveAccelerationStructureDescriptor::descriptor(); 124 | acc_desc.setGeometryDescriptors(Some(&empty_array)); 125 | let sizes = device.accelerationStructureSizesWithDescriptor(&acc_desc); 126 | let allocation_desc = AllocationCreateDesc::acceleration_structure_with_size( 127 | &device, 128 | "Test allocation (Acceleration structure)", 129 | sizes.accelerationStructureSize as u64, 130 | gpu_allocator::MemoryLocation::GpuOnly, 131 | ); 132 | let allocation = allocator.allocate(&allocation_desc).unwrap(); 133 | // SAFETY: We will only allocate objects on this heap within the returned offset and size 134 | let heap = unsafe { allocation.heap() }; 135 | let buffer = unsafe { 136 | heap.newAccelerationStructureWithSize_offset( 137 | allocation.size() as usize, 138 | allocation.offset() as usize, 139 | ) 140 | } 141 | .unwrap(); 142 | drop(buffer); 143 | allocator.free(&allocation).unwrap(); 144 | info!("Allocation and deallocation of Acceleration structure was successful."); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /examples/vulkan-buffer.rs: -------------------------------------------------------------------------------- 1 | use std::default::Default; 2 | 3 | use ash::vk; 4 | use gpu_allocator::{ 5 | vulkan::{AllocationCreateDesc, AllocationScheme, Allocator, AllocatorCreateDesc}, 6 | MemoryLocation, 7 | }; 8 | use log::info; 9 | 10 | fn main() { 11 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); 12 | 13 | let entry = unsafe { ash::Entry::load() }.unwrap(); 14 | 15 | // Create Vulkan instance 16 | let instance = { 17 | let app_name = c"Vulkan gpu-allocator test"; 18 | 19 | let appinfo = vk::ApplicationInfo::default() 20 | .application_name(app_name) 21 | .application_version(0) 22 | .engine_name(app_name) 23 | .engine_version(0) 24 | .api_version(vk::make_api_version(0, 1, 0, 0)); 25 | 26 | let layer_names_raw = [c"VK_LAYER_KHRONOS_validation".as_ptr()]; 27 | 28 | let create_info = vk::InstanceCreateInfo::default() 29 | .application_info(&appinfo) 30 | .enabled_layer_names(&layer_names_raw); 31 | 32 | unsafe { 33 | entry 34 | .create_instance(&create_info, None) 35 | .expect("Instance creation error") 36 | } 37 | }; 38 | 39 | // Look for vulkan physical device 40 | let (pdevice, queue_family_index) = { 41 | let pdevices = unsafe { 42 | instance 43 | .enumerate_physical_devices() 44 | .expect("Physical device error") 45 | }; 46 | pdevices 47 | .iter() 48 | .find_map(|pdevice| { 49 | unsafe { instance.get_physical_device_queue_family_properties(*pdevice) } 50 | .iter() 51 | .enumerate() 52 | .find_map(|(index, &info)| { 53 | let supports_graphics = info.queue_flags.contains(vk::QueueFlags::GRAPHICS); 54 | if supports_graphics { 55 | Some((*pdevice, index)) 56 | } else { 57 | None 58 | } 59 | }) 60 | }) 61 | .expect("Couldn't find suitable device.") 62 | }; 63 | 64 | // Create vulkan device 65 | let device = { 66 | let device_extension_names_raw = vec![]; 67 | let features = vk::PhysicalDeviceFeatures { 68 | shader_clip_distance: 1, 69 | ..Default::default() 70 | }; 71 | let priorities = [1.0]; 72 | 73 | let queue_info = vk::DeviceQueueCreateInfo::default() 74 | .queue_family_index(queue_family_index as u32) 75 | .queue_priorities(&priorities); 76 | 77 | let create_info = vk::DeviceCreateInfo::default() 78 | .queue_create_infos(std::slice::from_ref(&queue_info)) 79 | .enabled_extension_names(&device_extension_names_raw) 80 | .enabled_features(&features); 81 | 82 | unsafe { instance.create_device(pdevice, &create_info, None).unwrap() } 83 | }; 84 | 85 | // Setting up the allocator 86 | let mut allocator = Allocator::new(&AllocatorCreateDesc { 87 | instance: instance.clone(), 88 | device: device.clone(), 89 | physical_device: pdevice, 90 | debug_settings: Default::default(), 91 | buffer_device_address: false, 92 | allocation_sizes: Default::default(), 93 | }) 94 | .unwrap(); 95 | 96 | // Test allocating Gpu Only memory 97 | { 98 | let test_buffer_info = vk::BufferCreateInfo::default() 99 | .size(512) 100 | .usage(vk::BufferUsageFlags::STORAGE_BUFFER) 101 | .sharing_mode(vk::SharingMode::EXCLUSIVE); 102 | let test_buffer = unsafe { device.create_buffer(&test_buffer_info, None) }.unwrap(); 103 | let requirements = unsafe { device.get_buffer_memory_requirements(test_buffer) }; 104 | let location = MemoryLocation::GpuOnly; 105 | 106 | let allocation = allocator 107 | .allocate(&AllocationCreateDesc { 108 | requirements, 109 | location, 110 | linear: true, 111 | allocation_scheme: AllocationScheme::GpuAllocatorManaged, 112 | name: "Test allocation (Gpu Only)", 113 | }) 114 | .unwrap(); 115 | 116 | unsafe { 117 | device 118 | .bind_buffer_memory(test_buffer, allocation.memory(), allocation.offset()) 119 | .unwrap() 120 | }; 121 | 122 | allocator.free(allocation).unwrap(); 123 | 124 | unsafe { device.destroy_buffer(test_buffer, None) }; 125 | 126 | info!("Allocation and deallocation of GpuOnly memory was successful."); 127 | } 128 | 129 | // Test allocating Cpu to Gpu memory 130 | { 131 | let test_buffer_info = vk::BufferCreateInfo::default() 132 | .size(512) 133 | .usage(vk::BufferUsageFlags::STORAGE_BUFFER) 134 | .sharing_mode(vk::SharingMode::EXCLUSIVE); 135 | let test_buffer = unsafe { device.create_buffer(&test_buffer_info, None) }.unwrap(); 136 | let requirements = unsafe { device.get_buffer_memory_requirements(test_buffer) }; 137 | let location = MemoryLocation::CpuToGpu; 138 | 139 | let allocation = allocator 140 | .allocate(&AllocationCreateDesc { 141 | requirements, 142 | location, 143 | linear: true, 144 | allocation_scheme: AllocationScheme::GpuAllocatorManaged, 145 | name: "Test allocation (Cpu to Gpu)", 146 | }) 147 | .unwrap(); 148 | 149 | unsafe { 150 | device 151 | .bind_buffer_memory(test_buffer, allocation.memory(), allocation.offset()) 152 | .unwrap() 153 | }; 154 | 155 | allocator.free(allocation).unwrap(); 156 | 157 | unsafe { device.destroy_buffer(test_buffer, None) }; 158 | 159 | info!("Allocation and deallocation of CpuToGpu memory was successful."); 160 | } 161 | 162 | // Test allocating Gpu to Cpu memory 163 | { 164 | let test_buffer_info = vk::BufferCreateInfo::default() 165 | .size(512) 166 | .usage(vk::BufferUsageFlags::STORAGE_BUFFER) 167 | .sharing_mode(vk::SharingMode::EXCLUSIVE); 168 | let test_buffer = unsafe { device.create_buffer(&test_buffer_info, None) }.unwrap(); 169 | let requirements = unsafe { device.get_buffer_memory_requirements(test_buffer) }; 170 | let location = MemoryLocation::GpuToCpu; 171 | 172 | let allocation = allocator 173 | .allocate(&AllocationCreateDesc { 174 | requirements, 175 | location, 176 | linear: true, 177 | allocation_scheme: AllocationScheme::GpuAllocatorManaged, 178 | name: "Test allocation (Gpu to Cpu)", 179 | }) 180 | .unwrap(); 181 | 182 | unsafe { 183 | device 184 | .bind_buffer_memory(test_buffer, allocation.memory(), allocation.offset()) 185 | .unwrap() 186 | }; 187 | 188 | allocator.free(allocation).unwrap(); 189 | 190 | unsafe { device.destroy_buffer(test_buffer, None) }; 191 | 192 | info!("Allocation and deallocation of GpuToCpu memory was successful."); 193 | } 194 | 195 | drop(allocator); // Explicitly drop before destruction of device and instance. 196 | unsafe { device.destroy_device(None) }; 197 | unsafe { instance.destroy_instance(None) }; 198 | } 199 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "Release {{version}}" 2 | tag-message = "Release {{version}}" 3 | tag-name = "{{version}}" 4 | sign-commit = true 5 | sign-tag = true 6 | publish = false 7 | 8 | pre-release-replacements = [ 9 | { file = "README.md", search = "gpu-allocator = .*", replace = "{{crate_name}} = \"{{version}}\"" }, 10 | { file = "README.tpl", search = "gpu-allocator = .*", replace = "{{crate_name}} = \"{{version}}\"" }, 11 | ] 12 | -------------------------------------------------------------------------------- /src/allocator/dedicated_block_allocator/mod.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code, clippy::unwrap_used)] 2 | 3 | #[cfg(feature = "visualizer")] 4 | pub(crate) mod visualizer; 5 | 6 | use std::{backtrace::Backtrace, sync::Arc}; 7 | 8 | use log::{log, Level}; 9 | 10 | use super::{AllocationReport, AllocationType, SubAllocator, SubAllocatorBase}; 11 | use crate::{AllocationError, Result}; 12 | 13 | #[derive(Debug)] 14 | pub(crate) struct DedicatedBlockAllocator { 15 | size: u64, 16 | allocated: u64, 17 | /// Only used if [`crate::AllocatorDebugSettings::store_stack_traces`] is [`true`] 18 | name: Option, 19 | backtrace: Arc, 20 | } 21 | 22 | impl DedicatedBlockAllocator { 23 | pub(crate) fn new(size: u64) -> Self { 24 | Self { 25 | size, 26 | allocated: 0, 27 | name: None, 28 | backtrace: Arc::new(Backtrace::disabled()), 29 | } 30 | } 31 | } 32 | 33 | impl SubAllocatorBase for DedicatedBlockAllocator {} 34 | impl SubAllocator for DedicatedBlockAllocator { 35 | fn allocate( 36 | &mut self, 37 | size: u64, 38 | _alignment: u64, 39 | _allocation_type: AllocationType, 40 | _granularity: u64, 41 | name: &str, 42 | backtrace: Arc, 43 | ) -> Result<(u64, std::num::NonZeroU64)> { 44 | if self.allocated != 0 { 45 | return Err(AllocationError::OutOfMemory); 46 | } 47 | 48 | if self.size != size { 49 | return Err(AllocationError::Internal( 50 | "DedicatedBlockAllocator size must match allocation size.".into(), 51 | )); 52 | } 53 | 54 | self.allocated = size; 55 | self.name = Some(name.to_string()); 56 | self.backtrace = backtrace; 57 | 58 | #[allow(clippy::unwrap_used)] 59 | let dummy_id = std::num::NonZeroU64::new(1).unwrap(); 60 | Ok((0, dummy_id)) 61 | } 62 | 63 | fn free(&mut self, chunk_id: Option) -> Result<()> { 64 | if chunk_id != std::num::NonZeroU64::new(1) { 65 | Err(AllocationError::Internal("Chunk ID must be 1.".into())) 66 | } else { 67 | self.allocated = 0; 68 | Ok(()) 69 | } 70 | } 71 | 72 | fn rename_allocation( 73 | &mut self, 74 | chunk_id: Option, 75 | name: &str, 76 | ) -> Result<()> { 77 | if chunk_id != std::num::NonZeroU64::new(1) { 78 | Err(AllocationError::Internal("Chunk ID must be 1.".into())) 79 | } else { 80 | self.name = Some(name.into()); 81 | Ok(()) 82 | } 83 | } 84 | 85 | fn report_memory_leaks( 86 | &self, 87 | log_level: Level, 88 | memory_type_index: usize, 89 | memory_block_index: usize, 90 | ) { 91 | let empty = "".to_string(); 92 | let name = self.name.as_ref().unwrap_or(&empty); 93 | 94 | log!( 95 | log_level, 96 | r#"leak detected: {{ 97 | memory type: {} 98 | memory block: {} 99 | dedicated allocation: {{ 100 | size: 0x{:x}, 101 | name: {}, 102 | backtrace: {} 103 | }} 104 | }}"#, 105 | memory_type_index, 106 | memory_block_index, 107 | self.size, 108 | name, 109 | self.backtrace 110 | ) 111 | } 112 | 113 | fn report_allocations(&self) -> Vec { 114 | vec![AllocationReport { 115 | name: self 116 | .name 117 | .clone() 118 | .unwrap_or_else(|| "".to_owned()), 119 | offset: 0, 120 | size: self.size, 121 | #[cfg(feature = "visualizer")] 122 | backtrace: self.backtrace.clone(), 123 | }] 124 | } 125 | 126 | fn allocated(&self) -> u64 { 127 | self.allocated 128 | } 129 | 130 | fn supports_general_allocations(&self) -> bool { 131 | false 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/allocator/dedicated_block_allocator/visualizer.rs: -------------------------------------------------------------------------------- 1 | use super::DedicatedBlockAllocator; 2 | use crate::visualizer::SubAllocatorVisualizer; 3 | 4 | impl SubAllocatorVisualizer for DedicatedBlockAllocator { 5 | fn draw_base_info(&self, ui: &mut egui::Ui) { 6 | ui.label("Dedicated Block"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/allocator/free_list_allocator/mod.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code, clippy::unwrap_used)] 2 | 3 | #[cfg(feature = "visualizer")] 4 | pub(crate) mod visualizer; 5 | 6 | use std::{ 7 | backtrace::Backtrace, 8 | collections::{HashMap, HashSet}, 9 | sync::Arc, 10 | }; 11 | 12 | use log::{log, Level}; 13 | 14 | use super::{AllocationReport, AllocationType, SubAllocator, SubAllocatorBase}; 15 | use crate::{AllocationError, Result}; 16 | 17 | const USE_BEST_FIT: bool = true; 18 | 19 | fn align_down(val: u64, alignment: u64) -> u64 { 20 | val & !(alignment - 1u64) 21 | } 22 | 23 | fn align_up(val: u64, alignment: u64) -> u64 { 24 | align_down(val + alignment - 1u64, alignment) 25 | } 26 | 27 | #[derive(Debug)] 28 | pub(crate) struct MemoryChunk { 29 | pub(crate) chunk_id: std::num::NonZeroU64, 30 | pub(crate) size: u64, 31 | pub(crate) offset: u64, 32 | pub(crate) allocation_type: AllocationType, 33 | pub(crate) name: Option, 34 | /// Only used if [`crate::AllocatorDebugSettings::store_stack_traces`] is [`true`] 35 | pub(crate) backtrace: Arc, 36 | next: Option, 37 | prev: Option, 38 | } 39 | 40 | #[derive(Debug)] 41 | pub(crate) struct FreeListAllocator { 42 | size: u64, 43 | allocated: u64, 44 | pub(crate) chunk_id_counter: u64, 45 | pub(crate) chunks: HashMap, 46 | free_chunks: HashSet, 47 | } 48 | 49 | /// Test if two suballocations will overlap the same page. 50 | fn is_on_same_page(offset_a: u64, size_a: u64, offset_b: u64, page_size: u64) -> bool { 51 | let end_a = offset_a + size_a - 1; 52 | let end_page_a = align_down(end_a, page_size); 53 | let start_b = offset_b; 54 | let start_page_b = align_down(start_b, page_size); 55 | 56 | end_page_a == start_page_b 57 | } 58 | 59 | /// Test if two allocation types will be conflicting or not. 60 | fn has_granularity_conflict(type0: AllocationType, type1: AllocationType) -> bool { 61 | if type0 == AllocationType::Free || type1 == AllocationType::Free { 62 | return false; 63 | } 64 | 65 | type0 != type1 66 | } 67 | 68 | impl FreeListAllocator { 69 | pub(crate) fn new(size: u64) -> Self { 70 | #[allow(clippy::unwrap_used)] 71 | let initial_chunk_id = std::num::NonZeroU64::new(1).unwrap(); 72 | 73 | let mut chunks = HashMap::default(); 74 | chunks.insert( 75 | initial_chunk_id, 76 | MemoryChunk { 77 | chunk_id: initial_chunk_id, 78 | size, 79 | offset: 0, 80 | allocation_type: AllocationType::Free, 81 | name: None, 82 | backtrace: Arc::new(Backtrace::disabled()), 83 | prev: None, 84 | next: None, 85 | }, 86 | ); 87 | 88 | let mut free_chunks = HashSet::default(); 89 | free_chunks.insert(initial_chunk_id); 90 | 91 | Self { 92 | size, 93 | allocated: 0, 94 | // 0 is not allowed as a chunk ID, 1 is used by the initial chunk, next chunk is going to be 2. 95 | // The system well take the counter as the ID, and the increment the counter. 96 | chunk_id_counter: 2, 97 | chunks, 98 | free_chunks, 99 | } 100 | } 101 | 102 | /// Generates a new unique chunk ID 103 | fn get_new_chunk_id(&mut self) -> Result { 104 | if self.chunk_id_counter == u64::MAX { 105 | // End of chunk id counter reached, no more allocations are possible. 106 | return Err(AllocationError::OutOfMemory); 107 | } 108 | 109 | let id = self.chunk_id_counter; 110 | self.chunk_id_counter += 1; 111 | std::num::NonZeroU64::new(id).ok_or_else(|| { 112 | AllocationError::Internal("New chunk id was 0, which is not allowed.".into()) 113 | }) 114 | } 115 | /// Finds the specified `chunk_id` in the list of free chunks and removes if from the list 116 | fn remove_id_from_free_list(&mut self, chunk_id: std::num::NonZeroU64) { 117 | self.free_chunks.remove(&chunk_id); 118 | } 119 | /// Merges two adjacent chunks. Right chunk will be merged into the left chunk 120 | fn merge_free_chunks( 121 | &mut self, 122 | chunk_left: std::num::NonZeroU64, 123 | chunk_right: std::num::NonZeroU64, 124 | ) -> Result<()> { 125 | // Gather data from right chunk and remove it 126 | let (right_size, right_next) = { 127 | let chunk = self.chunks.remove(&chunk_right).ok_or_else(|| { 128 | AllocationError::Internal("Chunk ID not present in chunk list.".into()) 129 | })?; 130 | self.remove_id_from_free_list(chunk.chunk_id); 131 | 132 | (chunk.size, chunk.next) 133 | }; 134 | 135 | // Merge into left chunk 136 | { 137 | let chunk = self.chunks.get_mut(&chunk_left).ok_or_else(|| { 138 | AllocationError::Internal("Chunk ID not present in chunk list.".into()) 139 | })?; 140 | chunk.next = right_next; 141 | chunk.size += right_size; 142 | } 143 | 144 | // Patch pointers 145 | if let Some(right_next) = right_next { 146 | let chunk = self.chunks.get_mut(&right_next).ok_or_else(|| { 147 | AllocationError::Internal("Chunk ID not present in chunk list.".into()) 148 | })?; 149 | chunk.prev = Some(chunk_left); 150 | } 151 | 152 | Ok(()) 153 | } 154 | } 155 | 156 | impl SubAllocatorBase for FreeListAllocator {} 157 | impl SubAllocator for FreeListAllocator { 158 | fn allocate( 159 | &mut self, 160 | size: u64, 161 | alignment: u64, 162 | allocation_type: AllocationType, 163 | granularity: u64, 164 | name: &str, 165 | backtrace: Arc, 166 | ) -> Result<(u64, std::num::NonZeroU64)> { 167 | let free_size = self.size - self.allocated; 168 | if size > free_size { 169 | return Err(AllocationError::OutOfMemory); 170 | } 171 | 172 | let mut best_fit_id: Option = None; 173 | let mut best_offset = 0u64; 174 | let mut best_aligned_size = 0u64; 175 | let mut best_chunk_size = 0u64; 176 | 177 | for current_chunk_id in self.free_chunks.iter() { 178 | let current_chunk = self.chunks.get(current_chunk_id).ok_or_else(|| { 179 | AllocationError::Internal( 180 | "Chunk ID in free list is not present in chunk list.".into(), 181 | ) 182 | })?; 183 | 184 | if current_chunk.size < size { 185 | continue; 186 | } 187 | 188 | let mut offset = align_up(current_chunk.offset, alignment); 189 | 190 | if let Some(prev_idx) = current_chunk.prev { 191 | let previous = self.chunks.get(&prev_idx).ok_or_else(|| { 192 | AllocationError::Internal("Invalid previous chunk reference.".into()) 193 | })?; 194 | if is_on_same_page(previous.offset, previous.size, offset, granularity) 195 | && has_granularity_conflict(previous.allocation_type, allocation_type) 196 | { 197 | offset = align_up(offset, granularity); 198 | } 199 | } 200 | 201 | let padding = offset - current_chunk.offset; 202 | let aligned_size = padding + size; 203 | 204 | if aligned_size > current_chunk.size { 205 | continue; 206 | } 207 | 208 | if let Some(next_idx) = current_chunk.next { 209 | let next = self.chunks.get(&next_idx).ok_or_else(|| { 210 | AllocationError::Internal("Invalid next chunk reference.".into()) 211 | })?; 212 | if is_on_same_page(offset, size, next.offset, granularity) 213 | && has_granularity_conflict(allocation_type, next.allocation_type) 214 | { 215 | continue; 216 | } 217 | } 218 | 219 | if USE_BEST_FIT { 220 | if best_fit_id.is_none() || current_chunk.size < best_chunk_size { 221 | best_fit_id = Some(*current_chunk_id); 222 | best_aligned_size = aligned_size; 223 | best_offset = offset; 224 | 225 | best_chunk_size = current_chunk.size; 226 | }; 227 | } else { 228 | best_fit_id = Some(*current_chunk_id); 229 | best_aligned_size = aligned_size; 230 | best_offset = offset; 231 | 232 | best_chunk_size = current_chunk.size; 233 | break; 234 | } 235 | } 236 | 237 | let first_fit_id = best_fit_id.ok_or(AllocationError::OutOfMemory)?; 238 | 239 | let chunk_id = if best_chunk_size > best_aligned_size { 240 | let new_chunk_id = self.get_new_chunk_id()?; 241 | 242 | let new_chunk = { 243 | let free_chunk = self.chunks.get_mut(&first_fit_id).ok_or_else(|| { 244 | AllocationError::Internal("Chunk ID must be in chunk list.".into()) 245 | })?; 246 | let new_chunk = MemoryChunk { 247 | chunk_id: new_chunk_id, 248 | size: best_aligned_size, 249 | offset: free_chunk.offset, 250 | allocation_type, 251 | name: Some(name.to_string()), 252 | backtrace, 253 | prev: free_chunk.prev, 254 | next: Some(first_fit_id), 255 | }; 256 | 257 | free_chunk.prev = Some(new_chunk.chunk_id); 258 | free_chunk.offset += best_aligned_size; 259 | free_chunk.size -= best_aligned_size; 260 | new_chunk 261 | }; 262 | 263 | if let Some(prev_id) = new_chunk.prev { 264 | let prev_chunk = self.chunks.get_mut(&prev_id).ok_or_else(|| { 265 | AllocationError::Internal("Invalid previous chunk reference.".into()) 266 | })?; 267 | prev_chunk.next = Some(new_chunk.chunk_id); 268 | } 269 | 270 | self.chunks.insert(new_chunk_id, new_chunk); 271 | 272 | new_chunk_id 273 | } else { 274 | let chunk = self 275 | .chunks 276 | .get_mut(&first_fit_id) 277 | .ok_or_else(|| AllocationError::Internal("Invalid chunk reference.".into()))?; 278 | 279 | chunk.allocation_type = allocation_type; 280 | chunk.name = Some(name.to_string()); 281 | chunk.backtrace = backtrace; 282 | 283 | self.remove_id_from_free_list(first_fit_id); 284 | 285 | first_fit_id 286 | }; 287 | 288 | self.allocated += best_aligned_size; 289 | 290 | Ok((best_offset, chunk_id)) 291 | } 292 | 293 | fn free(&mut self, chunk_id: Option) -> Result<()> { 294 | let chunk_id = chunk_id 295 | .ok_or_else(|| AllocationError::Internal("Chunk ID must be a valid value.".into()))?; 296 | 297 | let (next_id, prev_id) = { 298 | let chunk = self.chunks.get_mut(&chunk_id).ok_or_else(|| { 299 | AllocationError::Internal( 300 | "Attempting to free chunk that is not in chunk list.".into(), 301 | ) 302 | })?; 303 | chunk.allocation_type = AllocationType::Free; 304 | chunk.name = None; 305 | chunk.backtrace = Arc::new(Backtrace::disabled()); 306 | 307 | self.allocated -= chunk.size; 308 | 309 | self.free_chunks.insert(chunk.chunk_id); 310 | 311 | (chunk.next, chunk.prev) 312 | }; 313 | 314 | if let Some(next_id) = next_id { 315 | if self.chunks[&next_id].allocation_type == AllocationType::Free { 316 | self.merge_free_chunks(chunk_id, next_id)?; 317 | } 318 | } 319 | 320 | if let Some(prev_id) = prev_id { 321 | if self.chunks[&prev_id].allocation_type == AllocationType::Free { 322 | self.merge_free_chunks(prev_id, chunk_id)?; 323 | } 324 | } 325 | Ok(()) 326 | } 327 | 328 | fn rename_allocation( 329 | &mut self, 330 | chunk_id: Option, 331 | name: &str, 332 | ) -> Result<()> { 333 | let chunk_id = chunk_id 334 | .ok_or_else(|| AllocationError::Internal("Chunk ID must be a valid value.".into()))?; 335 | 336 | let chunk = self.chunks.get_mut(&chunk_id).ok_or_else(|| { 337 | AllocationError::Internal( 338 | "Attempting to rename chunk that is not in chunk list.".into(), 339 | ) 340 | })?; 341 | 342 | if chunk.allocation_type == AllocationType::Free { 343 | return Err(AllocationError::Internal( 344 | "Attempting to rename a freed allocation.".into(), 345 | )); 346 | } 347 | 348 | chunk.name = Some(name.into()); 349 | 350 | Ok(()) 351 | } 352 | 353 | fn report_memory_leaks( 354 | &self, 355 | log_level: Level, 356 | memory_type_index: usize, 357 | memory_block_index: usize, 358 | ) { 359 | for (chunk_id, chunk) in self.chunks.iter() { 360 | if chunk.allocation_type == AllocationType::Free { 361 | continue; 362 | } 363 | let empty = "".to_string(); 364 | let name = chunk.name.as_ref().unwrap_or(&empty); 365 | 366 | log!( 367 | log_level, 368 | r#"leak detected: {{ 369 | memory type: {} 370 | memory block: {} 371 | chunk: {{ 372 | chunk_id: {}, 373 | size: 0x{:x}, 374 | offset: 0x{:x}, 375 | allocation_type: {:?}, 376 | name: {}, 377 | backtrace: {} 378 | }} 379 | }}"#, 380 | memory_type_index, 381 | memory_block_index, 382 | chunk_id, 383 | chunk.size, 384 | chunk.offset, 385 | chunk.allocation_type, 386 | name, 387 | chunk.backtrace 388 | ); 389 | } 390 | } 391 | 392 | fn report_allocations(&self) -> Vec { 393 | self.chunks 394 | .iter() 395 | .filter(|(_key, chunk)| chunk.allocation_type != AllocationType::Free) 396 | .map(|(_key, chunk)| AllocationReport { 397 | name: chunk 398 | .name 399 | .clone() 400 | .unwrap_or_else(|| "".to_owned()), 401 | offset: chunk.offset, 402 | size: chunk.size, 403 | #[cfg(feature = "visualizer")] 404 | backtrace: chunk.backtrace.clone(), 405 | }) 406 | .collect::>() 407 | } 408 | 409 | fn allocated(&self) -> u64 { 410 | self.allocated 411 | } 412 | 413 | fn supports_general_allocations(&self) -> bool { 414 | true 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /src/allocator/free_list_allocator/visualizer.rs: -------------------------------------------------------------------------------- 1 | use super::FreeListAllocator; 2 | use crate::visualizer::{ 3 | render_memory_chunks_ui, ColorScheme, MemoryChunksVisualizationSettings, SubAllocatorVisualizer, 4 | }; 5 | 6 | impl SubAllocatorVisualizer for FreeListAllocator { 7 | fn supports_visualization(&self) -> bool { 8 | true 9 | } 10 | 11 | fn draw_base_info(&self, ui: &mut egui::Ui) { 12 | ui.label("free list sub-allocator"); 13 | ui.label(format!("chunk count: {}", self.chunks.len())); 14 | ui.label(format!("chunk id counter: {}", self.chunk_id_counter)); 15 | } 16 | 17 | fn draw_visualization( 18 | &self, 19 | color_scheme: &ColorScheme, 20 | ui: &mut egui::Ui, 21 | settings: &MemoryChunksVisualizationSettings, 22 | ) { 23 | render_memory_chunks_ui(ui, color_scheme, settings, self.size, self.chunks.values()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/allocator/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{backtrace::Backtrace, fmt, ops::Range, sync::Arc}; 2 | 3 | use log::*; 4 | 5 | use crate::result::*; 6 | 7 | pub(crate) mod dedicated_block_allocator; 8 | pub(crate) use dedicated_block_allocator::DedicatedBlockAllocator; 9 | 10 | pub(crate) mod free_list_allocator; 11 | pub(crate) use free_list_allocator::FreeListAllocator; 12 | 13 | #[derive(PartialEq, Copy, Clone, Debug)] 14 | #[repr(u8)] 15 | pub(crate) enum AllocationType { 16 | Free, 17 | Linear, 18 | NonLinear, 19 | } 20 | 21 | impl AllocationType { 22 | #[cfg(feature = "visualizer")] 23 | pub fn as_str(self) -> &'static str { 24 | match self { 25 | Self::Free => "Free", 26 | Self::Linear => "Linear", 27 | Self::NonLinear => "Non-Linear", 28 | } 29 | } 30 | } 31 | 32 | /// Describes an allocation in the [`AllocatorReport`]. 33 | #[derive(Clone)] 34 | pub struct AllocationReport { 35 | /// The name provided to the `allocate()` function. 36 | pub name: String, 37 | /// The offset in bytes of the allocation in its memory block. 38 | pub offset: u64, 39 | /// The size in bytes of the allocation. 40 | pub size: u64, 41 | #[cfg(feature = "visualizer")] 42 | pub(crate) backtrace: Arc, 43 | } 44 | 45 | /// Describes a memory block in the [`AllocatorReport`]. 46 | #[derive(Clone)] 47 | pub struct MemoryBlockReport { 48 | /// The size in bytes of this memory block. 49 | pub size: u64, 50 | /// The range of allocations in [`AllocatorReport::allocations`] that are associated 51 | /// to this memory block. 52 | pub allocations: Range, 53 | } 54 | 55 | /// A report that can be generated for informational purposes using `Allocator::generate_report()`. 56 | #[derive(Clone)] 57 | pub struct AllocatorReport { 58 | /// All live allocations, sub-allocated from memory blocks. 59 | pub allocations: Vec, 60 | /// All memory blocks. 61 | pub blocks: Vec, 62 | /// Sum of the memory used by all allocations, in bytes. 63 | pub total_allocated_bytes: u64, 64 | /// Sum of the memory capacity of all memory blocks including unallocated regions, in bytes. 65 | pub total_capacity_bytes: u64, 66 | } 67 | 68 | impl fmt::Debug for AllocationReport { 69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 70 | let name = if !self.name.is_empty() { 71 | self.name.as_str() 72 | } else { 73 | "--" 74 | }; 75 | write!(f, "{name:?}: {}", fmt_bytes(self.size)) 76 | } 77 | } 78 | 79 | impl fmt::Debug for AllocatorReport { 80 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 81 | let mut allocations = self.allocations.clone(); 82 | allocations.sort_by_key(|alloc| std::cmp::Reverse(alloc.size)); 83 | 84 | let max_num_allocations_to_print = f.precision().unwrap_or(usize::MAX); 85 | allocations.truncate(max_num_allocations_to_print); 86 | 87 | f.debug_struct("AllocatorReport") 88 | .field( 89 | "summary", 90 | &std::format_args!( 91 | "{} / {}", 92 | fmt_bytes(self.total_allocated_bytes), 93 | fmt_bytes(self.total_capacity_bytes) 94 | ), 95 | ) 96 | .field("blocks", &self.blocks.len()) 97 | .field("allocations", &self.allocations.len()) 98 | .field("largest", &allocations.as_slice()) 99 | .finish() 100 | } 101 | } 102 | 103 | #[cfg(feature = "visualizer")] 104 | pub(crate) trait SubAllocatorBase: crate::visualizer::SubAllocatorVisualizer {} 105 | #[cfg(not(feature = "visualizer"))] 106 | pub(crate) trait SubAllocatorBase {} 107 | 108 | pub(crate) trait SubAllocator: SubAllocatorBase + fmt::Debug + Sync + Send { 109 | fn allocate( 110 | &mut self, 111 | size: u64, 112 | alignment: u64, 113 | allocation_type: AllocationType, 114 | granularity: u64, 115 | name: &str, 116 | backtrace: Arc, 117 | ) -> Result<(u64, std::num::NonZeroU64)>; 118 | 119 | fn free(&mut self, chunk_id: Option) -> Result<()>; 120 | 121 | fn rename_allocation( 122 | &mut self, 123 | chunk_id: Option, 124 | name: &str, 125 | ) -> Result<()>; 126 | 127 | fn report_memory_leaks( 128 | &self, 129 | log_level: Level, 130 | memory_type_index: usize, 131 | memory_block_index: usize, 132 | ); 133 | 134 | fn report_allocations(&self) -> Vec; 135 | 136 | #[must_use] 137 | fn supports_general_allocations(&self) -> bool; 138 | #[must_use] 139 | fn allocated(&self) -> u64; 140 | 141 | /// Helper function: reports if the suballocator is empty (meaning, having no allocations). 142 | #[must_use] 143 | fn is_empty(&self) -> bool { 144 | self.allocated() == 0 145 | } 146 | } 147 | 148 | pub(crate) fn fmt_bytes(mut amount: u64) -> String { 149 | const SUFFIX: [&str; 5] = ["B", "KB", "MB", "GB", "TB"]; 150 | 151 | let mut idx = 0; 152 | let mut print_amount = amount as f64; 153 | loop { 154 | if amount < 1024 { 155 | return format!("{:.2} {}", print_amount, SUFFIX[idx]); 156 | } 157 | 158 | print_amount = amount as f64 / 1024.0; 159 | amount /= 1024; 160 | idx += 1; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/d3d12/visualizer.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::new_without_default)] 2 | 3 | use windows::Win32::Graphics::Direct3D12::*; 4 | 5 | use super::Allocator; 6 | use crate::visualizer::{ 7 | render_allocation_reports_ui, AllocationReportVisualizeSettings, ColorScheme, 8 | MemoryChunksVisualizationSettings, 9 | }; 10 | 11 | struct AllocatorVisualizerBlockWindow { 12 | memory_type_index: usize, 13 | block_index: usize, 14 | settings: MemoryChunksVisualizationSettings, 15 | } 16 | 17 | impl AllocatorVisualizerBlockWindow { 18 | fn new(memory_type_index: usize, block_index: usize) -> Self { 19 | Self { 20 | memory_type_index, 21 | block_index, 22 | settings: Default::default(), 23 | } 24 | } 25 | } 26 | 27 | pub struct AllocatorVisualizer { 28 | selected_blocks: Vec, 29 | color_scheme: ColorScheme, 30 | breakdown_settings: AllocationReportVisualizeSettings, 31 | } 32 | 33 | fn format_heap_type(heap_type: D3D12_HEAP_TYPE) -> &'static str { 34 | let names = [ 35 | "D3D12_HEAP_TYPE_DEFAULT_INVALID", 36 | "D3D12_HEAP_TYPE_DEFAULT", 37 | "D3D12_HEAP_TYPE_UPLOAD", 38 | "D3D12_HEAP_TYPE_READBACK", 39 | "D3D12_HEAP_TYPE_CUSTOM", 40 | ]; 41 | 42 | names[heap_type.0 as usize] 43 | } 44 | 45 | fn format_cpu_page_property(prop: D3D12_CPU_PAGE_PROPERTY) -> &'static str { 46 | let names = [ 47 | "D3D12_CPU_PAGE_PROPERTY_UNKNOWN", 48 | "D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE", 49 | "D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE", 50 | "D3D12_CPU_PAGE_PROPERTY_WRITE_BACK", 51 | ]; 52 | 53 | names[prop.0 as usize] 54 | } 55 | 56 | fn format_memory_pool(pool: D3D12_MEMORY_POOL) -> &'static str { 57 | let names = [ 58 | "D3D12_MEMORY_POOL_UNKNOWN", 59 | "D3D12_MEMORY_POOL_L0", 60 | "D3D12_MEMORY_POOL_L1", 61 | ]; 62 | 63 | names[pool.0 as usize] 64 | } 65 | 66 | impl AllocatorVisualizer { 67 | pub fn new() -> Self { 68 | Self { 69 | selected_blocks: Vec::default(), 70 | color_scheme: ColorScheme::default(), 71 | breakdown_settings: Default::default(), 72 | } 73 | } 74 | 75 | pub fn set_color_scheme(&mut self, color_scheme: ColorScheme) { 76 | self.color_scheme = color_scheme; 77 | } 78 | 79 | pub fn render_memory_block_ui(&mut self, ui: &mut egui::Ui, alloc: &Allocator) { 80 | ui.collapsing( 81 | format!("Memory Types: ({} types)", alloc.memory_types.len()), 82 | |ui| { 83 | for (mem_type_idx, mem_type) in alloc.memory_types.iter().enumerate() { 84 | ui.collapsing( 85 | format!( 86 | "Type: {} ({} blocks)", 87 | mem_type_idx, 88 | mem_type.memory_blocks.len() 89 | ), 90 | |ui| { 91 | let mut total_block_size = 0; 92 | let mut total_allocated = 0; 93 | 94 | for block in mem_type.memory_blocks.iter().flatten() { 95 | total_block_size += block.size; 96 | total_allocated += block.sub_allocator.allocated(); 97 | } 98 | 99 | let active_block_count = mem_type 100 | .memory_blocks 101 | .iter() 102 | .filter(|block| block.is_some()) 103 | .count(); 104 | 105 | ui.label(format!("heap category: {:?}", mem_type.heap_category)); 106 | ui.label(format!( 107 | "Heap Type: {} ({})", 108 | format_heap_type(mem_type.heap_properties.Type), 109 | mem_type.heap_properties.Type.0 110 | )); 111 | ui.label(format!( 112 | "CpuPageProperty: {} ({})", 113 | format_cpu_page_property(mem_type.heap_properties.CPUPageProperty), 114 | mem_type.heap_properties.CPUPageProperty.0 115 | )); 116 | ui.label(format!( 117 | "MemoryPoolPreference: {} ({})", 118 | format_memory_pool(mem_type.heap_properties.MemoryPoolPreference), 119 | mem_type.heap_properties.MemoryPoolPreference.0 120 | )); 121 | ui.label(format!("total block size: {} KiB", total_block_size / 1024)); 122 | ui.label(format!("total allocated: {} KiB", total_allocated / 1024)); 123 | ui.label(format!( 124 | "committed resource allocations: {}", 125 | mem_type.committed_allocations.num_allocations 126 | )); 127 | ui.label(format!( 128 | "total committed resource allocations: {} KiB", 129 | mem_type.committed_allocations.total_size 130 | )); 131 | ui.label(format!("block count: {}", active_block_count)); 132 | 133 | for (block_idx, block) in mem_type.memory_blocks.iter().enumerate() { 134 | let Some(block) = block else { continue }; 135 | 136 | ui.collapsing(format!("Block: {}", block_idx), |ui| { 137 | ui.label(format!("size: {} KiB", block.size / 1024)); 138 | ui.label(format!( 139 | "allocated: {} KiB", 140 | block.sub_allocator.allocated() / 1024 141 | )); 142 | ui.label(format!("D3D12 heap: {:?}", block.heap)); 143 | block.sub_allocator.draw_base_info(ui); 144 | 145 | if block.sub_allocator.supports_visualization() 146 | && ui.button("visualize").clicked() 147 | && !self.selected_blocks.iter().any(|x| { 148 | x.memory_type_index == mem_type_idx 149 | && x.block_index == block_idx 150 | }) 151 | { 152 | self.selected_blocks.push( 153 | AllocatorVisualizerBlockWindow::new( 154 | mem_type_idx, 155 | block_idx, 156 | ), 157 | ); 158 | } 159 | }); 160 | } 161 | }, 162 | ); 163 | } 164 | }, 165 | ); 166 | } 167 | 168 | pub fn render_memory_block_window( 169 | &mut self, 170 | ctx: &egui::Context, 171 | allocator: &Allocator, 172 | open: &mut bool, 173 | ) { 174 | egui::Window::new("Allocator Memory Blocks") 175 | .open(open) 176 | .show(ctx, |ui| self.render_breakdown_ui(ui, allocator)); 177 | } 178 | 179 | pub fn render_memory_block_visualization_windows( 180 | &mut self, 181 | ctx: &egui::Context, 182 | allocator: &Allocator, 183 | ) { 184 | // Draw each window. 185 | let color_scheme = &self.color_scheme; 186 | 187 | self.selected_blocks.retain_mut(|window| { 188 | let mut open = true; 189 | 190 | egui::Window::new(format!( 191 | "Block Visualizer {}:{}", 192 | window.memory_type_index, window.block_index 193 | )) 194 | .default_size([1920.0 * 0.5, 1080.0 * 0.5]) 195 | .open(&mut open) 196 | .show(ctx, |ui| { 197 | let memblock = &allocator.memory_types[window.memory_type_index].memory_blocks 198 | [window.block_index] 199 | .as_ref(); 200 | if let Some(memblock) = memblock { 201 | ui.label(format!( 202 | "Memory type {}, Memory block {}, Block size: {} KiB", 203 | window.memory_type_index, 204 | window.block_index, 205 | memblock.size / 1024 206 | )); 207 | 208 | window 209 | .settings 210 | .ui(ui, allocator.debug_settings.store_stack_traces); 211 | 212 | ui.separator(); 213 | 214 | memblock 215 | .sub_allocator 216 | .draw_visualization(color_scheme, ui, &window.settings); 217 | } else { 218 | ui.label("Deallocated memory block"); 219 | } 220 | }); 221 | 222 | open 223 | }); 224 | } 225 | 226 | pub fn render_breakdown_ui(&mut self, ui: &mut egui::Ui, allocator: &Allocator) { 227 | render_allocation_reports_ui( 228 | ui, 229 | &mut self.breakdown_settings, 230 | allocator 231 | .memory_types 232 | .iter() 233 | .flat_map(|memory_type| memory_type.memory_blocks.iter()) 234 | .flatten() 235 | .flat_map(|memory_block| memory_block.sub_allocator.report_allocations()), 236 | ); 237 | } 238 | 239 | pub fn render_breakdown_window( 240 | &mut self, 241 | ctx: &egui::Context, 242 | allocator: &Allocator, 243 | open: &mut bool, 244 | ) { 245 | egui::Window::new("Allocator Breakdown") 246 | .open(open) 247 | .show(ctx, |ui| self.render_breakdown_ui(ui, allocator)); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a fully written in Rust memory allocator for Vulkan, DirectX 12 and Metal. 2 | //! 3 | //! # [Windows-rs] and [winapi] 4 | //! 5 | //! `gpu-allocator` recently migrated from [winapi] to [windows-rs] but still provides convenient helpers to convert to and from [winapi] types, enabled when compiling with the `public-winapi` crate feature. 6 | //! 7 | //! [Windows-rs]: https://github.com/microsoft/windows-rs 8 | //! [winapi]: https://github.com/retep998/winapi-rs 9 | //! 10 | //! # Setting up the Vulkan memory allocator 11 | //! 12 | //! ```no_run 13 | //! # #[cfg(feature = "vulkan")] 14 | //! # fn main() { 15 | //! use gpu_allocator::vulkan::*; 16 | //! # use ash::vk; 17 | //! # let device = todo!(); 18 | //! # let instance = todo!(); 19 | //! # let physical_device = todo!(); 20 | //! 21 | //! let mut allocator = Allocator::new(&AllocatorCreateDesc { 22 | //! instance, 23 | //! device, 24 | //! physical_device, 25 | //! debug_settings: Default::default(), 26 | //! buffer_device_address: true, // Ideally, check the BufferDeviceAddressFeatures struct. 27 | //! allocation_sizes: Default::default(), 28 | //! }); 29 | //! # } 30 | //! # #[cfg(not(feature = "vulkan"))] 31 | //! # fn main() {} 32 | //! ``` 33 | //! 34 | //! # Simple Vulkan allocation example 35 | //! 36 | //! ```no_run 37 | //! # #[cfg(feature = "vulkan")] 38 | //! # fn main() { 39 | //! use gpu_allocator::vulkan::*; 40 | //! use gpu_allocator::MemoryLocation; 41 | //! # use ash::vk; 42 | //! # let device = todo!(); 43 | //! # let instance = todo!(); 44 | //! # let physical_device = todo!(); 45 | //! # let mut allocator = Allocator::new(&AllocatorCreateDesc { 46 | //! # instance, 47 | //! # device, 48 | //! # physical_device, 49 | //! # debug_settings: Default::default(), 50 | //! # buffer_device_address: true, // Ideally, check the BufferDeviceAddressFeatures struct. 51 | //! # allocation_sizes: Default::default(), 52 | //! # }).unwrap(); 53 | //! 54 | //! // Setup vulkan info 55 | //! let vk_info = vk::BufferCreateInfo::default() 56 | //! .size(512) 57 | //! .usage(vk::BufferUsageFlags::STORAGE_BUFFER); 58 | //! 59 | //! let buffer = unsafe { device.create_buffer(&vk_info, None) }.unwrap(); 60 | //! let requirements = unsafe { device.get_buffer_memory_requirements(buffer) }; 61 | //! 62 | //! let allocation = allocator 63 | //! .allocate(&AllocationCreateDesc { 64 | //! name: "Example allocation", 65 | //! requirements, 66 | //! location: MemoryLocation::CpuToGpu, 67 | //! linear: true, // Buffers are always linear 68 | //! allocation_scheme: AllocationScheme::GpuAllocatorManaged, 69 | //! }).unwrap(); 70 | //! 71 | //! // Bind memory to the buffer 72 | //! unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()).unwrap() }; 73 | //! 74 | //! // Cleanup 75 | //! allocator.free(allocation).unwrap(); 76 | //! unsafe { device.destroy_buffer(buffer, None) }; 77 | //! # } 78 | //! # #[cfg(not(feature = "vulkan"))] 79 | //! # fn main() {} 80 | //! ``` 81 | //! 82 | //! # Setting up the D3D12 memory allocator 83 | //! 84 | //! ```no_run 85 | //! # #[cfg(feature = "d3d12")] 86 | //! # fn main() { 87 | //! use gpu_allocator::d3d12::*; 88 | //! # let device = todo!(); 89 | //! 90 | //! let mut allocator = Allocator::new(&AllocatorCreateDesc { 91 | //! device: ID3D12DeviceVersion::Device(device), 92 | //! debug_settings: Default::default(), 93 | //! allocation_sizes: Default::default(), 94 | //! }); 95 | //! # } 96 | //! # #[cfg(not(feature = "d3d12"))] 97 | //! # fn main() {} 98 | //! ``` 99 | //! 100 | //! # Simple d3d12 allocation example 101 | //! 102 | //! ```no_run 103 | //! # #[cfg(feature = "d3d12")] 104 | //! # fn main() -> windows::core::Result<()> { 105 | //! use gpu_allocator::d3d12::*; 106 | //! use gpu_allocator::MemoryLocation; 107 | //! # use windows::Win32::Graphics::{Dxgi, Direct3D12}; 108 | //! # let device = todo!(); 109 | //! 110 | //! # let mut allocator = Allocator::new(&AllocatorCreateDesc { 111 | //! # device: ID3D12DeviceVersion::Device(device), 112 | //! # debug_settings: Default::default(), 113 | //! # allocation_sizes: Default::default(), 114 | //! # }).unwrap(); 115 | //! 116 | //! let buffer_desc = Direct3D12::D3D12_RESOURCE_DESC { 117 | //! Dimension: Direct3D12::D3D12_RESOURCE_DIMENSION_BUFFER, 118 | //! Alignment: 0, 119 | //! Width: 512, 120 | //! Height: 1, 121 | //! DepthOrArraySize: 1, 122 | //! MipLevels: 1, 123 | //! Format: Dxgi::Common::DXGI_FORMAT_UNKNOWN, 124 | //! SampleDesc: Dxgi::Common::DXGI_SAMPLE_DESC { 125 | //! Count: 1, 126 | //! Quality: 0, 127 | //! }, 128 | //! Layout: Direct3D12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 129 | //! Flags: Direct3D12::D3D12_RESOURCE_FLAG_NONE, 130 | //! }; 131 | //! let allocation_desc = AllocationCreateDesc::from_d3d12_resource_desc( 132 | //! &allocator.device(), 133 | //! &buffer_desc, 134 | //! "Example allocation", 135 | //! MemoryLocation::GpuOnly, 136 | //! ); 137 | //! let allocation = allocator.allocate(&allocation_desc).unwrap(); 138 | //! let mut resource: Option = None; 139 | //! let hr = unsafe { 140 | //! device.CreatePlacedResource( 141 | //! allocation.heap(), 142 | //! allocation.offset(), 143 | //! &buffer_desc, 144 | //! Direct3D12::D3D12_RESOURCE_STATE_COMMON, 145 | //! None, 146 | //! &mut resource, 147 | //! ) 148 | //! }?; 149 | //! 150 | //! // Cleanup 151 | //! drop(resource); 152 | //! allocator.free(allocation).unwrap(); 153 | //! # Ok(()) 154 | //! # } 155 | //! # #[cfg(not(feature = "d3d12"))] 156 | //! # fn main() {} 157 | //! ``` 158 | //! 159 | //! # Setting up the Metal memory allocator 160 | //! 161 | //! ```no_run 162 | //! # #[cfg(feature = "metal")] 163 | //! # fn main() { 164 | //! use gpu_allocator::metal::*; 165 | //! # let device = objc2_metal::MTLCreateSystemDefaultDevice().expect("No MTLDevice found"); 166 | //! let mut allocator = Allocator::new(&AllocatorCreateDesc { 167 | //! device: device.clone(), 168 | //! debug_settings: Default::default(), 169 | //! allocation_sizes: Default::default(), 170 | //! }); 171 | //! # } 172 | //! # #[cfg(not(feature = "metal"))] 173 | //! # fn main() {} 174 | //! ``` 175 | //! 176 | //! # Simple Metal allocation example 177 | //! ```no_run 178 | //! # #[cfg(feature = "metal")] 179 | //! # fn main() { 180 | //! use gpu_allocator::metal::*; 181 | //! use gpu_allocator::MemoryLocation; 182 | //! # let device = objc2_metal::MTLCreateSystemDefaultDevice().expect("No MTLDevice found"); 183 | //! # let mut allocator = Allocator::new(&AllocatorCreateDesc { 184 | //! # device: device.clone(), 185 | //! # debug_settings: Default::default(), 186 | //! # allocation_sizes: Default::default(), 187 | //! # }) 188 | //! # .unwrap(); 189 | //! let allocation_desc = AllocationCreateDesc::buffer( 190 | //! &device, 191 | //! "Example allocation", 192 | //! 512, // size in bytes 193 | //! MemoryLocation::GpuOnly, 194 | //! ); 195 | //! let allocation = allocator.allocate(&allocation_desc).unwrap(); 196 | //! # use objc2_metal::MTLHeap; 197 | //! let heap = unsafe { allocation.heap() }; 198 | //! let resource = unsafe { 199 | //! heap.newBufferWithLength_options_offset( 200 | //! allocation.size() as usize, 201 | //! heap.resourceOptions(), 202 | //! allocation.offset() as usize, 203 | //! ) 204 | //! } 205 | //! .unwrap(); 206 | //! 207 | //! // Cleanup 208 | //! drop(resource); 209 | //! allocator.free(&allocation).unwrap(); 210 | //! # } 211 | //! # #[cfg(not(feature = "metal"))] 212 | //! # fn main() {} 213 | //! ``` 214 | #![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)] 215 | 216 | mod result; 217 | pub use result::*; 218 | 219 | pub(crate) mod allocator; 220 | 221 | pub use allocator::{AllocationReport, AllocatorReport, MemoryBlockReport}; 222 | 223 | #[cfg(feature = "visualizer")] 224 | pub mod visualizer; 225 | 226 | #[cfg(feature = "vulkan")] 227 | pub mod vulkan; 228 | 229 | #[cfg(all(windows, feature = "d3d12"))] 230 | pub mod d3d12; 231 | 232 | #[cfg(all(target_vendor = "apple", feature = "metal"))] 233 | pub mod metal; 234 | 235 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 236 | pub enum MemoryLocation { 237 | /// The allocated resource is stored at an unknown memory location; let the driver decide what's the best location 238 | Unknown, 239 | /// Store the allocation in GPU only accessible memory - typically this is the faster GPU resource and this should be 240 | /// where most of the allocations live. 241 | GpuOnly, 242 | /// Memory useful for uploading data to the GPU and potentially for constant buffers 243 | CpuToGpu, 244 | /// Memory useful for CPU readback of data 245 | GpuToCpu, 246 | } 247 | 248 | #[derive(Copy, Clone, Debug)] 249 | pub struct AllocatorDebugSettings { 250 | /// Logs out debugging information about the various heaps the current device has on startup 251 | pub log_memory_information: bool, 252 | /// Logs out all memory leaks on shutdown with log level Warn 253 | pub log_leaks_on_shutdown: bool, 254 | /// Stores a copy of the full backtrace for every allocation made, this makes it easier to debug leaks 255 | /// or other memory allocations, but storing stack traces has a RAM overhead so should be disabled 256 | /// in shipping applications. 257 | pub store_stack_traces: bool, 258 | /// Log out every allocation as it's being made with log level Debug, rather spammy so off by default 259 | pub log_allocations: bool, 260 | /// Log out every free that is being called with log level Debug, rather spammy so off by default 261 | pub log_frees: bool, 262 | /// Log out stack traces when either `log_allocations` or `log_frees` is enabled. 263 | pub log_stack_traces: bool, 264 | } 265 | 266 | impl Default for AllocatorDebugSettings { 267 | fn default() -> Self { 268 | Self { 269 | log_memory_information: false, 270 | log_leaks_on_shutdown: true, 271 | store_stack_traces: false, 272 | log_allocations: false, 273 | log_frees: false, 274 | log_stack_traces: false, 275 | } 276 | } 277 | } 278 | 279 | /// The sizes of the memory blocks that the allocator will create. 280 | /// 281 | /// Useful for tuning the allocator to your application's needs. For example most games will be fine with the default 282 | /// values, but eg. an app might want to use smaller block sizes to reduce the amount of memory used. 283 | /// 284 | /// Clamped between 4MB and 256MB, and rounds up to the nearest multiple of 4MB for alignment reasons. 285 | /// 286 | /// Note that these limits only apply to shared memory blocks that can hold multiple allocations. 287 | /// If an allocation does not fit within the corresponding maximum block size, it will be placed 288 | /// in a dedicated memory block holding only this allocation, without limitations other than what 289 | /// the underlying hardware and driver are able to provide. 290 | /// 291 | /// # Fixed or growable block size 292 | /// 293 | /// This structure represents ranges of allowed sizes for shared memory blocks. 294 | /// By default, if the upper bounds are not extended using `with_max_*_memblock_size`, 295 | /// the allocator will be configured to use a fixed memory block size for shared 296 | /// allocations. 297 | /// 298 | /// Otherwise, the allocator will pick a memory block size within the specifed 299 | /// range, depending on the number of existing allocations for the memory 300 | /// type. 301 | /// 302 | /// As a rule of thumb, the allocator will start with the minimum block size 303 | /// and double the size with each new allocation, up to the specified maximum 304 | /// block size. This growth is tracked independently for each memory type. 305 | /// The block size also decreases when blocks are deallocated. 306 | /// 307 | /// # Example 308 | /// 309 | /// ``` 310 | /// use gpu_allocator::AllocationSizes; 311 | /// const MB: u64 = 1024 * 1024; 312 | /// // This configuration uses fixed memory block sizes. 313 | /// let fixed = AllocationSizes::new(256 * MB, 64 * MB); 314 | /// 315 | /// // This configuration starts with 8MB memory blocks 316 | /// // and grows the block size of a given memory type each 317 | /// // time a new allocation is needed, up to a limit of 318 | /// // 256MB for device memory and 64MB for host memory. 319 | /// let growing = AllocationSizes::new(8 * MB, 8 * MB) 320 | /// .with_max_device_memblock_size(256 * MB) 321 | /// .with_max_host_memblock_size(64 * MB); 322 | /// ``` 323 | #[derive(Clone, Copy, Debug)] 324 | pub struct AllocationSizes { 325 | /// The initial size for device memory blocks. 326 | /// 327 | /// The size of new device memory blocks doubles each time a new block is needed, up to 328 | /// [`AllocationSizes::max_device_memblock_size`]. 329 | /// 330 | /// Defaults to 256MB. 331 | min_device_memblock_size: u64, 332 | /// The maximum size for device memory blocks. 333 | /// 334 | /// Defaults to the value of [`AllocationSizes::min_device_memblock_size`]. 335 | max_device_memblock_size: u64, 336 | /// The initial size for host memory blocks. 337 | /// 338 | /// The size of new host memory blocks doubles each time a new block is needed, up to 339 | /// [`AllocationSizes::max_host_memblock_size`]. 340 | /// 341 | /// Defaults to 64MB. 342 | min_host_memblock_size: u64, 343 | /// The maximum size for host memory blocks. 344 | /// 345 | /// Defaults to the value of [`AllocationSizes::min_host_memblock_size`]. 346 | max_host_memblock_size: u64, 347 | } 348 | 349 | impl AllocationSizes { 350 | /// Sets the minimum device and host memory block sizes. 351 | /// 352 | /// The maximum block sizes are initialized to the minimum sizes and 353 | /// can be increased using [`AllocationSizes::with_max_device_memblock_size`] and 354 | /// [`AllocationSizes::with_max_host_memblock_size`]. 355 | pub fn new(device_memblock_size: u64, host_memblock_size: u64) -> Self { 356 | let device_memblock_size = Self::adjust_memblock_size(device_memblock_size, "Device"); 357 | let host_memblock_size = Self::adjust_memblock_size(host_memblock_size, "Host"); 358 | 359 | Self { 360 | min_device_memblock_size: device_memblock_size, 361 | max_device_memblock_size: device_memblock_size, 362 | min_host_memblock_size: host_memblock_size, 363 | max_host_memblock_size: host_memblock_size, 364 | } 365 | } 366 | 367 | /// Sets the maximum device memblock size, in bytes. 368 | pub fn with_max_device_memblock_size(mut self, size: u64) -> Self { 369 | self.max_device_memblock_size = 370 | Self::adjust_memblock_size(size, "Device").max(self.min_device_memblock_size); 371 | 372 | self 373 | } 374 | 375 | /// Sets the maximum host memblock size, in bytes. 376 | pub fn with_max_host_memblock_size(mut self, size: u64) -> Self { 377 | self.max_host_memblock_size = 378 | Self::adjust_memblock_size(size, "Host").max(self.min_host_memblock_size); 379 | 380 | self 381 | } 382 | 383 | fn adjust_memblock_size(size: u64, kind: &str) -> u64 { 384 | const MB: u64 = 1024 * 1024; 385 | 386 | let size = size.clamp(4 * MB, 256 * MB); 387 | 388 | if size % (4 * MB) == 0 { 389 | return size; 390 | } 391 | 392 | let val = size / (4 * MB) + 1; 393 | let new_size = val * 4 * MB; 394 | log::warn!( 395 | "{kind} memory block size must be a multiple of 4MB, clamping to {}MB", 396 | new_size / MB 397 | ); 398 | 399 | new_size 400 | } 401 | 402 | /// Used internally to decide the size of a shared memory block 403 | /// based within the allowed range, based on the number of 404 | /// existing allocations. The more blocks there already are 405 | /// (where the requested allocation didn't fit), the larger 406 | /// the returned memory block size is going to be (up to 407 | /// `max_*_memblock_size`). 408 | pub(crate) fn get_memblock_size(&self, is_host: bool, count: usize) -> u64 { 409 | let (min_size, max_size) = if is_host { 410 | (self.min_host_memblock_size, self.max_host_memblock_size) 411 | } else { 412 | (self.min_device_memblock_size, self.max_device_memblock_size) 413 | }; 414 | 415 | // The ranges are clamped to 4MB..256MB so we never need to 416 | // shift by more than 7 bits. Clamping here to avoid having 417 | // to worry about overflows. 418 | let shift = count.min(7) as u64; 419 | (min_size << shift).min(max_size) 420 | } 421 | } 422 | 423 | impl Default for AllocationSizes { 424 | fn default() -> Self { 425 | const MB: u64 = 1024 * 1024; 426 | Self { 427 | min_device_memblock_size: 256 * MB, 428 | max_device_memblock_size: 256 * MB, 429 | min_host_memblock_size: 64 * MB, 430 | max_host_memblock_size: 64 * MB, 431 | } 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /src/metal/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{backtrace::Backtrace, sync::Arc}; 2 | 3 | #[cfg(feature = "visualizer")] 4 | mod visualizer; 5 | #[cfg(feature = "visualizer")] 6 | pub use visualizer::AllocatorVisualizer; 7 | 8 | use log::debug; 9 | use objc2::{rc::Retained, runtime::ProtocolObject}; 10 | use objc2_foundation::NSString; 11 | use objc2_metal::{ 12 | MTLCPUCacheMode, MTLDevice, MTLHeap, MTLHeapDescriptor, MTLHeapType, MTLResourceOptions, 13 | MTLStorageMode, MTLTextureDescriptor, 14 | }; 15 | 16 | use crate::{ 17 | allocator::{self, AllocatorReport, MemoryBlockReport}, 18 | AllocationError, AllocationSizes, AllocatorDebugSettings, MemoryLocation, Result, 19 | }; 20 | 21 | fn memory_location_to_metal(location: MemoryLocation) -> MTLResourceOptions { 22 | match location { 23 | MemoryLocation::GpuOnly => MTLResourceOptions::StorageModePrivate, 24 | MemoryLocation::CpuToGpu | MemoryLocation::GpuToCpu | MemoryLocation::Unknown => { 25 | MTLResourceOptions::StorageModeShared 26 | } 27 | } 28 | } 29 | 30 | #[derive(Debug)] 31 | pub struct Allocation { 32 | chunk_id: Option, 33 | offset: u64, 34 | size: u64, 35 | memory_block_index: usize, 36 | memory_type_index: usize, 37 | heap: Retained>, 38 | name: Option>, 39 | } 40 | 41 | impl Allocation { 42 | /// Returns the [`MTLHeap`] object that is backing this allocation. 43 | /// 44 | /// This heap object can be shared with multiple other allocations and shouldn't be allocated from 45 | /// without this library, because that will lead to undefined behavior. 46 | /// 47 | /// # Safety 48 | /// When allocating new buffers, textures, or other resources on this [`MTLHeap`], be sure to 49 | /// pass [`Self::offset()`] and not exceed [`Self::size()`] to not allocate new resources on top 50 | /// of existing [`Allocation`]s. 51 | /// 52 | /// Also, this [`Allocation`] must not be [`Allocator::free()`]d while such a created resource 53 | /// on this [`MTLHeap`] is still live. 54 | pub unsafe fn heap(&self) -> &ProtocolObject { 55 | &self.heap 56 | } 57 | 58 | /// Returns the size of the allocation 59 | pub fn size(&self) -> u64 { 60 | self.size 61 | } 62 | 63 | /// Returns the offset of the allocation on the [`MTLHeap`]. 64 | /// 65 | /// Since all [`Allocation`]s are suballocated within a [`MTLHeap`], this offset always needs to 66 | /// be supplied. See the safety documentation on [`Self::heap()`]. 67 | pub fn offset(&self) -> u64 { 68 | self.offset 69 | } 70 | 71 | pub fn name(&self) -> Option<&str> { 72 | self.name.as_deref() 73 | } 74 | 75 | fn is_null(&self) -> bool { 76 | self.chunk_id.is_none() 77 | } 78 | } 79 | 80 | #[derive(Clone, Debug)] 81 | pub struct AllocationCreateDesc<'a> { 82 | /// Name of the allocation, for tracking and debugging purposes 83 | pub name: &'a str, 84 | /// Location where the memory allocation should be stored 85 | pub location: MemoryLocation, 86 | pub size: u64, 87 | pub alignment: u64, 88 | } 89 | 90 | impl<'a> AllocationCreateDesc<'a> { 91 | pub fn buffer( 92 | device: &ProtocolObject, 93 | name: &'a str, 94 | length: u64, 95 | location: MemoryLocation, 96 | ) -> Self { 97 | let size_and_align = device.heapBufferSizeAndAlignWithLength_options( 98 | length as usize, 99 | memory_location_to_metal(location), 100 | ); 101 | Self { 102 | name, 103 | location, 104 | size: size_and_align.size as u64, 105 | alignment: size_and_align.align as u64, 106 | } 107 | } 108 | 109 | pub fn texture( 110 | device: &ProtocolObject, 111 | name: &'a str, 112 | desc: &MTLTextureDescriptor, 113 | ) -> Self { 114 | let size_and_align = device.heapTextureSizeAndAlignWithDescriptor(desc); 115 | Self { 116 | name, 117 | location: match desc.storageMode() { 118 | MTLStorageMode::Shared | MTLStorageMode::Managed | MTLStorageMode::Memoryless => { 119 | MemoryLocation::Unknown 120 | } 121 | MTLStorageMode::Private => MemoryLocation::GpuOnly, 122 | MTLStorageMode(mode /* @ 4.. */) => todo!("Unknown storage mode {mode}"), 123 | }, 124 | size: size_and_align.size as u64, 125 | alignment: size_and_align.align as u64, 126 | } 127 | } 128 | 129 | pub fn acceleration_structure_with_size( 130 | device: &ProtocolObject, 131 | name: &'a str, 132 | size: u64, // TODO: usize 133 | location: MemoryLocation, 134 | ) -> Self { 135 | // TODO: See if we can mark this function as safe, after checking what happens if size is too large? 136 | // What other preconditions need to be upheld? 137 | let size_and_align = 138 | unsafe { device.heapAccelerationStructureSizeAndAlignWithSize(size as usize) }; 139 | Self { 140 | name, 141 | location, 142 | size: size_and_align.size as u64, 143 | alignment: size_and_align.align as u64, 144 | } 145 | } 146 | } 147 | 148 | pub struct Allocator { 149 | device: Retained>, 150 | debug_settings: AllocatorDebugSettings, 151 | memory_types: Vec, 152 | allocation_sizes: AllocationSizes, 153 | } 154 | 155 | impl std::fmt::Debug for Allocator { 156 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 157 | self.generate_report().fmt(f) 158 | } 159 | } 160 | 161 | #[derive(Debug)] 162 | pub struct AllocatorCreateDesc { 163 | pub device: Retained>, 164 | pub debug_settings: AllocatorDebugSettings, 165 | pub allocation_sizes: AllocationSizes, 166 | } 167 | 168 | #[derive(Debug)] 169 | pub struct CommittedAllocationStatistics { 170 | pub num_allocations: usize, 171 | pub total_size: u64, 172 | } 173 | 174 | #[derive(Debug)] 175 | struct MemoryBlock { 176 | heap: Retained>, 177 | size: u64, 178 | sub_allocator: Box, 179 | } 180 | 181 | impl MemoryBlock { 182 | fn new( 183 | device: &ProtocolObject, 184 | size: u64, 185 | heap_descriptor: &MTLHeapDescriptor, 186 | dedicated: bool, 187 | memory_location: MemoryLocation, 188 | ) -> Result { 189 | heap_descriptor.setSize(size as usize); 190 | 191 | let heap = device 192 | .newHeapWithDescriptor(heap_descriptor) 193 | .ok_or_else(|| AllocationError::Internal("No MTLHeap was returned".to_string()))?; 194 | 195 | heap.setLabel(Some(&NSString::from_str(&format!( 196 | "MemoryBlock {memory_location:?}" 197 | )))); 198 | 199 | let sub_allocator: Box = if dedicated { 200 | Box::new(allocator::DedicatedBlockAllocator::new(size)) 201 | } else { 202 | Box::new(allocator::FreeListAllocator::new(size)) 203 | }; 204 | 205 | Ok(Self { 206 | heap, 207 | size, 208 | sub_allocator, 209 | }) 210 | } 211 | } 212 | 213 | #[derive(Debug)] 214 | struct MemoryType { 215 | memory_blocks: Vec>, 216 | _committed_allocations: CommittedAllocationStatistics, 217 | memory_location: MemoryLocation, 218 | heap_properties: Retained, 219 | memory_type_index: usize, 220 | active_general_blocks: usize, 221 | } 222 | 223 | impl MemoryType { 224 | fn allocate( 225 | &mut self, 226 | device: &ProtocolObject, 227 | desc: &AllocationCreateDesc<'_>, 228 | backtrace: Arc, 229 | allocation_sizes: &AllocationSizes, 230 | ) -> Result { 231 | let allocation_type = allocator::AllocationType::Linear; 232 | 233 | let is_host = self.heap_properties.storageMode() != MTLStorageMode::Private; 234 | let memblock_size = allocation_sizes.get_memblock_size(is_host, self.active_general_blocks); 235 | 236 | let size = desc.size; 237 | let alignment = desc.alignment; 238 | 239 | // Create a dedicated block for large memory allocations 240 | if size > memblock_size { 241 | let mem_block = MemoryBlock::new( 242 | device, 243 | size, 244 | &self.heap_properties, 245 | true, 246 | self.memory_location, 247 | )?; 248 | 249 | let block_index = self.memory_blocks.iter().position(|block| block.is_none()); 250 | let block_index = match block_index { 251 | Some(i) => { 252 | self.memory_blocks[i].replace(mem_block); 253 | i 254 | } 255 | None => { 256 | self.memory_blocks.push(Some(mem_block)); 257 | self.memory_blocks.len() - 1 258 | } 259 | }; 260 | 261 | let mem_block = self.memory_blocks[block_index] 262 | .as_mut() 263 | .ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?; 264 | 265 | let (offset, chunk_id) = mem_block.sub_allocator.allocate( 266 | size, 267 | alignment, 268 | allocation_type, 269 | 1, 270 | desc.name, 271 | backtrace, 272 | )?; 273 | 274 | return Ok(Allocation { 275 | chunk_id: Some(chunk_id), 276 | size, 277 | offset, 278 | memory_block_index: block_index, 279 | memory_type_index: self.memory_type_index, 280 | heap: mem_block.heap.clone(), 281 | name: Some(desc.name.into()), 282 | }); 283 | } 284 | 285 | let mut empty_block_index = None; 286 | for (mem_block_i, mem_block) in self.memory_blocks.iter_mut().enumerate().rev() { 287 | if let Some(mem_block) = mem_block { 288 | let allocation = mem_block.sub_allocator.allocate( 289 | size, 290 | alignment, 291 | allocation_type, 292 | 1, 293 | desc.name, 294 | backtrace.clone(), 295 | ); 296 | 297 | match allocation { 298 | Ok((offset, chunk_id)) => { 299 | return Ok(Allocation { 300 | chunk_id: Some(chunk_id), 301 | offset, 302 | size, 303 | memory_block_index: mem_block_i, 304 | memory_type_index: self.memory_type_index, 305 | heap: mem_block.heap.clone(), 306 | name: Some(desc.name.into()), 307 | }); 308 | } 309 | Err(AllocationError::OutOfMemory) => {} // Block is full, continue search. 310 | Err(err) => return Err(err), // Unhandled error, return. 311 | } 312 | } else if empty_block_index.is_none() { 313 | empty_block_index = Some(mem_block_i); 314 | } 315 | } 316 | 317 | let new_memory_block = MemoryBlock::new( 318 | device, 319 | memblock_size, 320 | &self.heap_properties, 321 | false, 322 | self.memory_location, 323 | )?; 324 | 325 | let new_block_index = if let Some(block_index) = empty_block_index { 326 | self.memory_blocks[block_index] = Some(new_memory_block); 327 | block_index 328 | } else { 329 | self.memory_blocks.push(Some(new_memory_block)); 330 | self.memory_blocks.len() - 1 331 | }; 332 | 333 | self.active_general_blocks += 1; 334 | 335 | let mem_block = self.memory_blocks[new_block_index] 336 | .as_mut() 337 | .ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?; 338 | let allocation = mem_block.sub_allocator.allocate( 339 | size, 340 | alignment, 341 | allocation_type, 342 | 1, 343 | desc.name, 344 | backtrace, 345 | ); 346 | let (offset, chunk_id) = match allocation { 347 | Err(AllocationError::OutOfMemory) => Err(AllocationError::Internal( 348 | "Allocation that must succeed failed. This is a bug in the allocator.".into(), 349 | )), 350 | a => a, 351 | }?; 352 | 353 | Ok(Allocation { 354 | chunk_id: Some(chunk_id), 355 | offset, 356 | size, 357 | memory_block_index: new_block_index, 358 | memory_type_index: self.memory_type_index, 359 | heap: mem_block.heap.clone(), 360 | name: Some(desc.name.into()), 361 | }) 362 | } 363 | 364 | fn free(&mut self, allocation: &Allocation) -> Result<()> { 365 | let block_idx = allocation.memory_block_index; 366 | 367 | let mem_block = self.memory_blocks[block_idx] 368 | .as_mut() 369 | .ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?; 370 | 371 | mem_block.sub_allocator.free(allocation.chunk_id)?; 372 | 373 | if mem_block.sub_allocator.is_empty() { 374 | if mem_block.sub_allocator.supports_general_allocations() { 375 | if self.active_general_blocks > 1 { 376 | let block = self.memory_blocks[block_idx].take(); 377 | if block.is_none() { 378 | return Err(AllocationError::Internal( 379 | "Memory block must be Some.".into(), 380 | )); 381 | } 382 | // Note that `block` will be destroyed on `drop` here 383 | 384 | self.active_general_blocks -= 1; 385 | } 386 | } else { 387 | let block = self.memory_blocks[block_idx].take(); 388 | if block.is_none() { 389 | return Err(AllocationError::Internal( 390 | "Memory block must be Some.".into(), 391 | )); 392 | } 393 | // Note that `block` will be destroyed on `drop` here 394 | } 395 | } 396 | 397 | Ok(()) 398 | } 399 | } 400 | 401 | impl Allocator { 402 | pub fn new(desc: &AllocatorCreateDesc) -> Result { 403 | let heap_types = [ 404 | (MemoryLocation::GpuOnly, { 405 | let heap_desc = unsafe { MTLHeapDescriptor::new() }; 406 | heap_desc.setCpuCacheMode(MTLCPUCacheMode::DefaultCache); 407 | heap_desc.setStorageMode(MTLStorageMode::Private); 408 | heap_desc.setType(MTLHeapType::Placement); 409 | heap_desc 410 | }), 411 | (MemoryLocation::CpuToGpu, { 412 | let heap_desc = unsafe { MTLHeapDescriptor::new() }; 413 | heap_desc.setCpuCacheMode(MTLCPUCacheMode::WriteCombined); 414 | heap_desc.setStorageMode(MTLStorageMode::Shared); 415 | heap_desc.setType(MTLHeapType::Placement); 416 | heap_desc 417 | }), 418 | (MemoryLocation::GpuToCpu, { 419 | let heap_desc = unsafe { MTLHeapDescriptor::new() }; 420 | heap_desc.setCpuCacheMode(MTLCPUCacheMode::DefaultCache); 421 | heap_desc.setStorageMode(MTLStorageMode::Shared); 422 | heap_desc.setType(MTLHeapType::Placement); 423 | heap_desc 424 | }), 425 | ]; 426 | 427 | let memory_types = heap_types 428 | .into_iter() 429 | .enumerate() 430 | .map(|(i, (memory_location, heap_descriptor))| MemoryType { 431 | memory_blocks: vec![], 432 | _committed_allocations: CommittedAllocationStatistics { 433 | num_allocations: 0, 434 | total_size: 0, 435 | }, 436 | memory_location, 437 | heap_properties: heap_descriptor, 438 | memory_type_index: i, 439 | active_general_blocks: 0, 440 | }) 441 | .collect(); 442 | 443 | Ok(Self { 444 | device: desc.device.clone(), 445 | debug_settings: desc.debug_settings, 446 | memory_types, 447 | allocation_sizes: desc.allocation_sizes, 448 | }) 449 | } 450 | 451 | pub fn allocate(&mut self, desc: &AllocationCreateDesc<'_>) -> Result { 452 | let size = desc.size; 453 | let alignment = desc.alignment; 454 | 455 | let backtrace = Arc::new(if self.debug_settings.store_stack_traces { 456 | Backtrace::force_capture() 457 | } else { 458 | Backtrace::disabled() 459 | }); 460 | 461 | if self.debug_settings.log_allocations { 462 | debug!( 463 | "Allocating `{}` of {} bytes with an alignment of {}.", 464 | &desc.name, size, alignment 465 | ); 466 | if self.debug_settings.log_stack_traces { 467 | let backtrace = Backtrace::force_capture(); 468 | debug!("Allocation stack trace: {}", backtrace); 469 | } 470 | } 471 | 472 | if size == 0 || !alignment.is_power_of_two() { 473 | return Err(AllocationError::InvalidAllocationCreateDesc); 474 | } 475 | 476 | // Find memory type 477 | let memory_type = self 478 | .memory_types 479 | .iter_mut() 480 | .find(|memory_type| { 481 | // Is location compatible 482 | desc.location == MemoryLocation::Unknown 483 | || desc.location == memory_type.memory_location 484 | }) 485 | .ok_or(AllocationError::NoCompatibleMemoryTypeFound)?; 486 | 487 | memory_type.allocate(&self.device, desc, backtrace, &self.allocation_sizes) 488 | } 489 | 490 | pub fn free(&mut self, allocation: &Allocation) -> Result<()> { 491 | if self.debug_settings.log_frees { 492 | let name = allocation.name.as_deref().unwrap_or(""); 493 | debug!("Freeing `{}`.", name); 494 | if self.debug_settings.log_stack_traces { 495 | let backtrace = Backtrace::force_capture(); 496 | debug!("Free stack trace: {}", backtrace); 497 | } 498 | } 499 | 500 | if allocation.is_null() { 501 | return Ok(()); 502 | } 503 | self.memory_types[allocation.memory_type_index].free(allocation)?; 504 | Ok(()) 505 | } 506 | 507 | /// Returns heaps for all memory blocks 508 | pub fn heaps(&self) -> impl Iterator> { 509 | self.memory_types.iter().flat_map(|memory_type| { 510 | memory_type 511 | .memory_blocks 512 | .iter() 513 | .flatten() 514 | .map(|block| block.heap.as_ref()) 515 | }) 516 | } 517 | 518 | pub fn generate_report(&self) -> AllocatorReport { 519 | let mut allocations = vec![]; 520 | let mut blocks = vec![]; 521 | let mut total_capacity_bytes = 0; 522 | 523 | for memory_type in &self.memory_types { 524 | for block in memory_type.memory_blocks.iter().flatten() { 525 | total_capacity_bytes += block.size; 526 | let first_allocation = allocations.len(); 527 | allocations.extend(block.sub_allocator.report_allocations()); 528 | blocks.push(MemoryBlockReport { 529 | size: block.size, 530 | allocations: first_allocation..allocations.len(), 531 | }); 532 | } 533 | } 534 | 535 | let total_allocated_bytes = allocations.iter().map(|report| report.size).sum(); 536 | 537 | AllocatorReport { 538 | allocations, 539 | blocks, 540 | total_allocated_bytes, 541 | total_capacity_bytes, 542 | } 543 | } 544 | 545 | /// Current total capacity of memory blocks allocated on the device, in bytes 546 | pub fn capacity(&self) -> u64 { 547 | let mut total_capacity_bytes = 0; 548 | 549 | for memory_type in &self.memory_types { 550 | for block in memory_type.memory_blocks.iter().flatten() { 551 | total_capacity_bytes += block.size; 552 | } 553 | } 554 | 555 | total_capacity_bytes 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /src/metal/visualizer.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::new_without_default)] 2 | 3 | use super::Allocator; 4 | use crate::visualizer::{ 5 | render_allocation_reports_ui, AllocationReportVisualizeSettings, ColorScheme, 6 | MemoryChunksVisualizationSettings, 7 | }; 8 | 9 | struct AllocatorVisualizerBlockWindow { 10 | memory_type_index: usize, 11 | block_index: usize, 12 | settings: MemoryChunksVisualizationSettings, 13 | } 14 | impl AllocatorVisualizerBlockWindow { 15 | fn new(memory_type_index: usize, block_index: usize) -> Self { 16 | Self { 17 | memory_type_index, 18 | block_index, 19 | settings: Default::default(), 20 | } 21 | } 22 | } 23 | 24 | pub struct AllocatorVisualizer { 25 | selected_blocks: Vec, 26 | color_scheme: ColorScheme, 27 | breakdown_settings: AllocationReportVisualizeSettings, 28 | } 29 | 30 | impl AllocatorVisualizer { 31 | pub fn new() -> Self { 32 | Self { 33 | selected_blocks: Vec::default(), 34 | color_scheme: ColorScheme::default(), 35 | breakdown_settings: Default::default(), 36 | } 37 | } 38 | 39 | pub fn set_color_scheme(&mut self, color_scheme: ColorScheme) { 40 | self.color_scheme = color_scheme; 41 | } 42 | 43 | pub fn render_memory_block_ui(&mut self, ui: &mut egui::Ui, alloc: &Allocator) { 44 | ui.collapsing( 45 | format!("Memory Types: ({} types)", alloc.memory_types.len()), 46 | |ui| { 47 | for (mem_type_idx, mem_type) in alloc.memory_types.iter().enumerate() { 48 | ui.collapsing( 49 | format!( 50 | "Type: {} ({} blocks)", 51 | mem_type_idx, 52 | mem_type.memory_blocks.len(), 53 | ), 54 | |ui| { 55 | let mut total_block_size = 0; 56 | let mut total_allocated = 0; 57 | 58 | for block in mem_type.memory_blocks.iter().flatten() { 59 | total_block_size += block.size; 60 | total_allocated += block.sub_allocator.allocated(); 61 | } 62 | 63 | let active_block_count = mem_type 64 | .memory_blocks 65 | .iter() 66 | .filter(|block| block.is_some()) 67 | .count(); 68 | 69 | ui.label(format!("properties: {:?}", mem_type.heap_properties)); 70 | ui.label(format!("memory type index: {}", mem_type.memory_type_index)); 71 | ui.label(format!("total block size: {} KiB", total_block_size / 1024)); 72 | ui.label(format!("total allocated: {} KiB", total_allocated / 1024)); 73 | ui.label(format!("block count: {}", active_block_count)); 74 | 75 | for (block_idx, block) in mem_type.memory_blocks.iter().enumerate() { 76 | let Some(block) = block else { continue }; 77 | 78 | ui.collapsing(format!("Block: {}", block_idx), |ui| { 79 | ui.label(format!("size: {} KiB", block.size / 1024)); 80 | ui.label(format!( 81 | "allocated: {} KiB", 82 | block.sub_allocator.allocated() / 1024 83 | )); 84 | ui.label(format!("Heap: {:?}", &block.heap)); 85 | 86 | block.sub_allocator.draw_base_info(ui); 87 | 88 | if block.sub_allocator.supports_visualization() 89 | && ui.button("visualize").clicked() 90 | && !self.selected_blocks.iter().any(|x| { 91 | x.memory_type_index == mem_type_idx 92 | && x.block_index == block_idx 93 | }) 94 | { 95 | self.selected_blocks.push( 96 | AllocatorVisualizerBlockWindow::new( 97 | mem_type_idx, 98 | block_idx, 99 | ), 100 | ); 101 | } 102 | }); 103 | } 104 | }, 105 | ); 106 | } 107 | }, 108 | ); 109 | } 110 | 111 | pub fn render_memory_block_window( 112 | &mut self, 113 | ctx: &egui::Context, 114 | allocator: &Allocator, 115 | open: &mut bool, 116 | ) { 117 | egui::Window::new("Allocator Memory Blocks") 118 | .open(open) 119 | .show(ctx, |ui| self.render_breakdown_ui(ui, allocator)); 120 | } 121 | 122 | pub fn render_memory_block_visualization_windows( 123 | &mut self, 124 | ctx: &egui::Context, 125 | allocator: &Allocator, 126 | ) { 127 | // Draw each window. 128 | let color_scheme = &self.color_scheme; 129 | 130 | self.selected_blocks.retain_mut(|window| { 131 | let mut open = true; 132 | 133 | egui::Window::new(format!( 134 | "Block Visualizer {}:{}", 135 | window.memory_type_index, window.block_index 136 | )) 137 | .default_size([1920.0 * 0.5, 1080.0 * 0.5]) 138 | .open(&mut open) 139 | .show(ctx, |ui| { 140 | let memblock = &allocator.memory_types[window.memory_type_index].memory_blocks 141 | [window.block_index] 142 | .as_ref(); 143 | if let Some(memblock) = memblock { 144 | ui.label(format!( 145 | "Memory type {}, Memory block {}, Block size: {} KiB", 146 | window.memory_type_index, 147 | window.block_index, 148 | memblock.size / 1024 149 | )); 150 | 151 | window 152 | .settings 153 | .ui(ui, allocator.debug_settings.store_stack_traces); 154 | 155 | ui.separator(); 156 | 157 | memblock 158 | .sub_allocator 159 | .draw_visualization(color_scheme, ui, &window.settings); 160 | } else { 161 | ui.label("Deallocated memory block"); 162 | } 163 | }); 164 | 165 | open 166 | }); 167 | } 168 | 169 | pub fn render_breakdown_ui(&mut self, ui: &mut egui::Ui, allocator: &Allocator) { 170 | render_allocation_reports_ui( 171 | ui, 172 | &mut self.breakdown_settings, 173 | allocator 174 | .memory_types 175 | .iter() 176 | .flat_map(|memory_type| memory_type.memory_blocks.iter()) 177 | .flatten() 178 | .flat_map(|memory_block| memory_block.sub_allocator.report_allocations()), 179 | ); 180 | } 181 | 182 | pub fn render_breakdown_window( 183 | &mut self, 184 | ctx: &egui::Context, 185 | allocator: &Allocator, 186 | open: &mut bool, 187 | ) { 188 | egui::Window::new("Allocator Breakdown") 189 | .open(open) 190 | .show(ctx, |ui| self.render_breakdown_ui(ui, allocator)); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/result.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum AllocationError { 5 | #[error("Out of memory")] 6 | OutOfMemory, 7 | #[error("Failed to map memory: {0}")] 8 | FailedToMap(String), 9 | #[error("No compatible memory type available")] 10 | NoCompatibleMemoryTypeFound, 11 | #[error("Invalid AllocationCreateDesc")] 12 | InvalidAllocationCreateDesc, 13 | #[error("Invalid AllocatorCreateDesc {0}")] 14 | InvalidAllocatorCreateDesc(String), 15 | #[error("Internal error: {0}")] 16 | Internal(String), 17 | #[error("Initial `BARRIER_LAYOUT` needs at least `Device10`")] 18 | BarrierLayoutNeedsDevice10, 19 | #[error("Castable formats require enhanced barriers")] 20 | CastableFormatsRequiresEnhancedBarriers, 21 | #[error("Castable formats require at least `Device12`")] 22 | CastableFormatsRequiresAtLeastDevice12, 23 | } 24 | 25 | pub type Result = ::std::result::Result; 26 | -------------------------------------------------------------------------------- /src/visualizer/allocation_reports.rs: -------------------------------------------------------------------------------- 1 | use std::backtrace::BacktraceStatus; 2 | 3 | use egui::{Label, Response, Sense, Ui, WidgetText}; 4 | use egui_extras::{Column, TableBuilder}; 5 | 6 | use crate::allocator::{fmt_bytes, AllocationReport}; 7 | 8 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 9 | pub(crate) enum AllocationReportVisualizeSorting { 10 | #[default] 11 | None, 12 | Idx, 13 | Name, 14 | Size, 15 | } 16 | 17 | #[derive(Debug, Default)] 18 | pub(crate) struct AllocationReportVisualizeSettings { 19 | pub filter: String, 20 | pub sorting: AllocationReportVisualizeSorting, 21 | pub ascending: bool, 22 | } 23 | 24 | pub(crate) fn render_allocation_reports_ui( 25 | ui: &mut Ui, 26 | settings: &mut AllocationReportVisualizeSettings, 27 | allocations: impl IntoIterator, 28 | ) { 29 | ui.horizontal(|ui| { 30 | ui.label("Filter"); 31 | ui.text_edit_singleline(&mut settings.filter); 32 | }); 33 | let breakdown_filter = settings.filter.to_lowercase(); 34 | 35 | let mut allocations = allocations 36 | .into_iter() 37 | .enumerate() 38 | .filter(|(_, report)| report.name.to_lowercase().contains(&breakdown_filter)) 39 | .collect::>(); 40 | let total_size_under_filter: u64 = allocations.iter().map(|a| a.1.size).sum(); 41 | 42 | ui.label(format!("Total: {}", fmt_bytes(total_size_under_filter))); 43 | 44 | let row_height = ui.text_style_height(&egui::TextStyle::Body); 45 | let table = TableBuilder::new(ui) 46 | .striped(true) 47 | .resizable(true) 48 | .column(Column::exact(30.0)) 49 | .column(Column::initial(300.0).at_least(200.0).clip(true)) 50 | .column(Column::exact(70.0)); 51 | 52 | fn header_button(ui: &mut Ui, label: &str) -> Response { 53 | let label = WidgetText::from(label).strong(); 54 | let label = Label::new(label).sense(Sense::click()); 55 | ui.add(label) 56 | } 57 | 58 | let table = table.header(row_height, |mut row| { 59 | row.col(|ui| { 60 | if header_button(ui, "Idx").clicked() { 61 | if settings.sorting == AllocationReportVisualizeSorting::Idx { 62 | settings.ascending = !settings.ascending; 63 | } else { 64 | settings.sorting = AllocationReportVisualizeSorting::Idx; 65 | settings.ascending = false; 66 | } 67 | } 68 | }); 69 | row.col(|ui| { 70 | if header_button(ui, "Name").clicked() { 71 | if settings.sorting == AllocationReportVisualizeSorting::Name { 72 | settings.ascending = !settings.ascending; 73 | } else { 74 | settings.sorting = AllocationReportVisualizeSorting::Name; 75 | settings.ascending = false; 76 | } 77 | } 78 | }); 79 | row.col(|ui| { 80 | if header_button(ui, "Size").clicked() { 81 | if settings.sorting == AllocationReportVisualizeSorting::Size { 82 | settings.ascending = !settings.ascending; 83 | } else { 84 | settings.sorting = AllocationReportVisualizeSorting::Size; 85 | settings.ascending = false; 86 | } 87 | } 88 | }); 89 | }); 90 | 91 | match (settings.sorting, settings.ascending) { 92 | (AllocationReportVisualizeSorting::None, _) => {} 93 | (AllocationReportVisualizeSorting::Idx, true) => allocations.sort_by_key(|(idx, _)| *idx), 94 | (AllocationReportVisualizeSorting::Idx, false) => { 95 | allocations.sort_by_key(|(idx, _)| std::cmp::Reverse(*idx)) 96 | } 97 | (AllocationReportVisualizeSorting::Name, true) => { 98 | allocations.sort_by(|(_, alloc1), (_, alloc2)| alloc1.name.cmp(&alloc2.name)) 99 | } 100 | (AllocationReportVisualizeSorting::Name, false) => { 101 | allocations.sort_by(|(_, alloc1), (_, alloc2)| alloc1.name.cmp(&alloc2.name).reverse()) 102 | } 103 | (AllocationReportVisualizeSorting::Size, true) => { 104 | allocations.sort_by_key(|(_, alloc)| alloc.size) 105 | } 106 | (AllocationReportVisualizeSorting::Size, false) => { 107 | allocations.sort_by_key(|(_, alloc)| std::cmp::Reverse(alloc.size)) 108 | } 109 | } 110 | 111 | table.body(|mut body| { 112 | for (idx, alloc) in allocations { 113 | body.row(row_height, |mut row| { 114 | let AllocationReport { 115 | name, 116 | size, 117 | backtrace, 118 | .. 119 | } = alloc; 120 | 121 | row.col(|ui| { 122 | ui.label(idx.to_string()); 123 | }); 124 | 125 | let resp = row.col(|ui| { 126 | ui.label(name); 127 | }); 128 | 129 | if backtrace.status() == BacktraceStatus::Captured { 130 | resp.1.on_hover_ui(|ui| { 131 | ui.label(backtrace.to_string()); 132 | }); 133 | } 134 | 135 | row.col(|ui| { 136 | ui.label(fmt_bytes(size)); 137 | }); 138 | }); 139 | } 140 | }); 141 | } 142 | -------------------------------------------------------------------------------- /src/visualizer/memory_chunks.rs: -------------------------------------------------------------------------------- 1 | use std::backtrace::BacktraceStatus; 2 | 3 | use egui::{Color32, DragValue, Rect, ScrollArea, Sense, Ui, Vec2}; 4 | 5 | use super::ColorScheme; 6 | use crate::allocator::free_list_allocator::MemoryChunk; 7 | 8 | pub(crate) struct MemoryChunksVisualizationSettings { 9 | pub width_in_bytes: u64, 10 | pub show_backtraces: bool, 11 | } 12 | 13 | impl Default for MemoryChunksVisualizationSettings { 14 | fn default() -> Self { 15 | Self { 16 | width_in_bytes: 1024, 17 | show_backtraces: false, 18 | } 19 | } 20 | } 21 | 22 | impl MemoryChunksVisualizationSettings { 23 | pub fn ui(&mut self, ui: &mut Ui, store_stack_traces: bool) { 24 | if store_stack_traces { 25 | ui.checkbox(&mut self.show_backtraces, "Show backtraces"); 26 | } 27 | 28 | // Slider for changing the 'zoom' level of the visualizer. 29 | const BYTES_PER_UNIT_MIN: i32 = 1; 30 | const BYTES_PER_UNIT_MAX: i32 = 1024 * 1024; 31 | 32 | ui.horizontal(|ui| { 33 | ui.add( 34 | DragValue::new(&mut self.width_in_bytes) 35 | .clamp_range(BYTES_PER_UNIT_MIN..=BYTES_PER_UNIT_MAX) 36 | .speed(10.0), 37 | ); 38 | ui.label("Bytes per line"); 39 | }); 40 | } 41 | } 42 | 43 | pub(crate) fn render_memory_chunks_ui<'a>( 44 | ui: &mut Ui, 45 | color_scheme: &ColorScheme, 46 | settings: &MemoryChunksVisualizationSettings, 47 | total_size_in_bytes: u64, 48 | data: impl IntoIterator, 49 | ) { 50 | let line_height = ui.text_style_height(&egui::TextStyle::Body); 51 | let number_of_rows = 52 | (total_size_in_bytes as f32 / settings.width_in_bytes as f32).ceil() as usize; 53 | 54 | ScrollArea::new([false, true]).show_rows(ui, line_height, number_of_rows, |ui, range| { 55 | // Let range be in bytes 56 | let start_in_bytes = range.start as u64 * settings.width_in_bytes; 57 | let end_in_bytes = range.end as u64 * settings.width_in_bytes; 58 | 59 | let mut data = data 60 | .into_iter() 61 | .filter(|chunk| { 62 | (chunk.offset + chunk.size) > start_in_bytes && chunk.offset < end_in_bytes 63 | }) 64 | .collect::>(); 65 | data.sort_by_key(|chunk| chunk.offset); 66 | 67 | let screen_width = ui.available_width(); 68 | let mut cursor_idx = 0; 69 | let mut bytes_required = data[cursor_idx].offset + data[cursor_idx].size - start_in_bytes; 70 | 71 | for _ in range { 72 | ui.horizontal(|ui| { 73 | let mut bytes_left = settings.width_in_bytes; 74 | let mut cursor = ui.cursor().min; 75 | 76 | while cursor_idx < data.len() && bytes_left > 0 { 77 | // Block is depleted, so reset for more chunks 78 | while bytes_required == 0 { 79 | cursor_idx += 1; 80 | if cursor_idx < data.len() { 81 | bytes_required = data[cursor_idx].size; 82 | } 83 | } 84 | 85 | let bytes_used = bytes_required.min(bytes_left); 86 | let width_used = 87 | bytes_used as f32 * screen_width / settings.width_in_bytes as f32; 88 | 89 | // Draw the rectangle 90 | let resp = ui.allocate_rect( 91 | Rect::from_min_size(cursor, Vec2::new(width_used, line_height)), 92 | Sense::click(), 93 | ); 94 | 95 | if ui.is_rect_visible(resp.rect) { 96 | ui.painter().rect( 97 | resp.rect, 98 | egui::Rounding::ZERO, 99 | color_scheme 100 | .get_allocation_type_color(data[cursor_idx].allocation_type), 101 | egui::Stroke::new(1.0, Color32::BLACK), 102 | ); 103 | 104 | resp.on_hover_ui_at_pointer(|ui| { 105 | let chunk = &data[cursor_idx]; 106 | ui.label(format!("id: {}", chunk.chunk_id)); 107 | ui.label(format!("offset: 0x{:x}", chunk.offset)); 108 | ui.label(format!("size: 0x{:x}", chunk.size)); 109 | ui.label(format!( 110 | "allocation_type: {}", 111 | chunk.allocation_type.as_str() 112 | )); 113 | if let Some(name) = &chunk.name { 114 | ui.label(format!("name: {}", name)); 115 | } 116 | if settings.show_backtraces 117 | && chunk.backtrace.status() == BacktraceStatus::Captured 118 | { 119 | ui.label(chunk.backtrace.to_string()); 120 | } 121 | }); 122 | } 123 | 124 | // Update our cursors 125 | cursor.x += width_used; 126 | bytes_left -= bytes_used; 127 | bytes_required -= bytes_used; 128 | } 129 | }); 130 | } 131 | }); 132 | } 133 | -------------------------------------------------------------------------------- /src/visualizer/mod.rs: -------------------------------------------------------------------------------- 1 | use egui::{Color32, Ui}; 2 | 3 | mod allocation_reports; 4 | mod memory_chunks; 5 | 6 | pub(crate) use allocation_reports::*; 7 | pub(crate) use memory_chunks::*; 8 | 9 | use crate::allocator::AllocationType; 10 | 11 | pub const DEFAULT_COLOR_ALLOCATION_TYPE_FREE: Color32 = Color32::from_rgb(159, 159, 159); // gray 12 | pub const DEFAULT_COLOR_ALLOCATION_TYPE_LINEAR: Color32 = Color32::from_rgb(91, 206, 250); // blue 13 | pub const DEFAULT_COLOR_ALLOCATION_TYPE_NON_LINEAR: Color32 = Color32::from_rgb(250, 169, 184); // pink 14 | 15 | #[derive(Clone)] 16 | pub struct ColorScheme { 17 | pub free_color: Color32, 18 | pub linear_color: Color32, 19 | pub non_linear_color: Color32, 20 | } 21 | 22 | impl Default for ColorScheme { 23 | fn default() -> Self { 24 | Self { 25 | free_color: DEFAULT_COLOR_ALLOCATION_TYPE_FREE, 26 | linear_color: DEFAULT_COLOR_ALLOCATION_TYPE_LINEAR, 27 | non_linear_color: DEFAULT_COLOR_ALLOCATION_TYPE_NON_LINEAR, 28 | } 29 | } 30 | } 31 | 32 | impl ColorScheme { 33 | pub(crate) fn get_allocation_type_color(&self, allocation_type: AllocationType) -> Color32 { 34 | match allocation_type { 35 | AllocationType::Free => self.free_color, 36 | AllocationType::Linear => self.linear_color, 37 | AllocationType::NonLinear => self.non_linear_color, 38 | } 39 | } 40 | } 41 | 42 | pub(crate) trait SubAllocatorVisualizer { 43 | fn supports_visualization(&self) -> bool { 44 | false 45 | } 46 | fn draw_base_info(&self, ui: &mut Ui) { 47 | ui.label("No sub allocator information available"); 48 | } 49 | fn draw_visualization( 50 | &self, 51 | _color_scheme: &ColorScheme, 52 | _ui: &mut Ui, 53 | _settings: &MemoryChunksVisualizationSettings, 54 | ) { 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/vulkan/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "visualizer")] 2 | mod visualizer; 3 | use std::{backtrace::Backtrace, fmt, marker::PhantomData, sync::Arc}; 4 | 5 | use ash::vk; 6 | use log::{debug, Level}; 7 | #[cfg(feature = "visualizer")] 8 | pub use visualizer::AllocatorVisualizer; 9 | 10 | use super::allocator; 11 | use crate::{ 12 | allocator::{AllocatorReport, MemoryBlockReport}, 13 | AllocationError, AllocationSizes, AllocatorDebugSettings, MemoryLocation, Result, 14 | }; 15 | 16 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 17 | pub enum AllocationScheme { 18 | /// Perform a dedicated, driver-managed allocation for the given buffer, allowing 19 | /// it to perform optimizations on this type of allocation. 20 | DedicatedBuffer(vk::Buffer), 21 | /// Perform a dedicated, driver-managed allocation for the given image, allowing 22 | /// it to perform optimizations on this type of allocation. 23 | DedicatedImage(vk::Image), 24 | /// The memory for this resource will be allocated and managed by gpu-allocator. 25 | GpuAllocatorManaged, 26 | } 27 | 28 | #[derive(Clone, Debug)] 29 | pub struct AllocationCreateDesc<'a> { 30 | /// Name of the allocation, for tracking and debugging purposes 31 | pub name: &'a str, 32 | /// Vulkan memory requirements for an allocation 33 | pub requirements: vk::MemoryRequirements, 34 | /// Location where the memory allocation should be stored 35 | pub location: MemoryLocation, 36 | /// If the resource is linear (buffer / linear texture) or a regular (tiled) texture. 37 | pub linear: bool, 38 | /// Determines how this allocation should be managed. 39 | pub allocation_scheme: AllocationScheme, 40 | } 41 | 42 | /// Wrapper type to only mark a raw pointer [`Send`] + [`Sync`] without having to 43 | /// mark the entire [`Allocation`] as such, instead relying on the compiler to 44 | /// auto-implement this or fail if fields are added that violate this constraint 45 | #[derive(Clone, Copy, Debug)] 46 | pub(crate) struct SendSyncPtr(std::ptr::NonNull); 47 | // Sending is fine because mapped_ptr does not change based on the thread we are in 48 | unsafe impl Send for SendSyncPtr {} 49 | // Sync is also okay because Sending &Allocation is safe: a mutable reference 50 | // to the data in mapped_ptr is never exposed while `self` is immutably borrowed. 51 | // In order to break safety guarantees, the user needs to `unsafe`ly dereference 52 | // `mapped_ptr` themselves. 53 | unsafe impl Sync for SendSyncPtr {} 54 | 55 | pub struct AllocatorCreateDesc { 56 | pub instance: ash::Instance, 57 | pub device: ash::Device, 58 | pub physical_device: vk::PhysicalDevice, 59 | pub debug_settings: AllocatorDebugSettings, 60 | pub buffer_device_address: bool, 61 | pub allocation_sizes: AllocationSizes, 62 | } 63 | 64 | /// A piece of allocated memory. 65 | /// 66 | /// Could be contained in its own individual underlying memory object or as a sub-region 67 | /// of a larger allocation. 68 | /// 69 | /// # Copying data into a CPU-mapped [`Allocation`] 70 | /// 71 | /// You'll very likely want to copy data into CPU-mapped [`Allocation`]s in order to send that data to the GPU. 72 | /// Doing this data transfer correctly without invoking undefined behavior can be quite fraught and non-obvious[\[1\]]. 73 | /// 74 | /// To help you do this correctly, [`Allocation`] implements [`presser::Slab`], which means you can directly 75 | /// pass it in to many of `presser`'s [helper functions] (for example, [`copy_from_slice_to_offset`]). 76 | /// 77 | /// In most cases, this will work perfectly. However, note that if you try to use an [`Allocation`] as a 78 | /// [`Slab`] and it is not valid to do so (if it is not CPU-mapped or if its `size > isize::MAX`), 79 | /// you will cause a panic. If you aren't sure about these conditions, you may use [`Allocation::try_as_mapped_slab`]. 80 | /// 81 | /// ## Example 82 | /// 83 | /// Say we've created an [`Allocation`] called `my_allocation`, which is CPU-mapped. 84 | /// ```ignore 85 | /// let mut my_allocation: Allocation = my_allocator.allocate(...)?; 86 | /// ``` 87 | /// 88 | /// And we want to fill it with some data in the form of a `my_gpu_data: Vec`, defined as such: 89 | /// 90 | /// ```ignore 91 | /// // note that this is size(12) but align(16), thus we have 4 padding bytes. 92 | /// // this would mean a `&[MyGpuVector]` is invalid to cast as a `&[u8]`, but 93 | /// // we can still use `presser` to copy it directly in a valid manner. 94 | /// #[repr(C, align(16))] 95 | /// #[derive(Clone, Copy)] 96 | /// struct MyGpuVertex { 97 | /// x: f32, 98 | /// y: f32, 99 | /// z: f32, 100 | /// } 101 | /// 102 | /// let my_gpu_data: Vec = make_vertex_data(); 103 | /// ``` 104 | /// 105 | /// Depending on how the data we're copying will be used, the Vulkan device may have a minimum 106 | /// alignment requirement for that data: 107 | /// 108 | /// ```ignore 109 | /// let min_gpu_align = my_vulkan_device_specifications.min_alignment_thing; 110 | /// ``` 111 | /// 112 | /// Finally, we can use [`presser::copy_from_slice_to_offset_with_align`] to perform the copy, 113 | /// simply passing `&mut my_allocation` since [`Allocation`] implements [`Slab`]. 114 | /// 115 | /// ```ignore 116 | /// let copy_record = presser::copy_from_slice_to_offset_with_align( 117 | /// &my_gpu_data[..], // a slice containing all elements of my_gpu_data 118 | /// &mut my_allocation, // our Allocation 119 | /// 0, // start as close to the beginning of the allocation as possible 120 | /// min_gpu_align, // the minimum alignment we queried previously 121 | /// )?; 122 | /// ``` 123 | /// 124 | /// It's important to note that the data may not have actually been copied starting at the requested 125 | /// `start_offset` (0 in the example above) depending on the alignment of the underlying allocation 126 | /// as well as the alignment requirements of `MyGpuVector` and the `min_gpu_align` we passed in. Thus, 127 | /// we can query the `copy_record` for the actual starting offset: 128 | /// 129 | /// ```ignore 130 | /// let actual_data_start_offset = copy_record.copy_start_offset; 131 | /// ``` 132 | /// 133 | /// ## Safety 134 | /// 135 | /// It is technically not fully safe to use an [`Allocation`] as a [`presser::Slab`] because we can't validate that the 136 | /// GPU is not using the data in the buffer while `self` is borrowed. However, trying 137 | /// to validate this statically is really hard and the community has basically decided that 138 | /// requiring `unsafe` for functions like this creates too much "unsafe-noise", ultimately making it 139 | /// harder to debug more insidious unsafety that is unrelated to GPU-CPU sync issues. 140 | /// 141 | /// So, as would always be the case, you must ensure the GPU 142 | /// is not using the data in `self` for the duration that you hold the returned [`MappedAllocationSlab`]. 143 | /// 144 | /// [`Slab`]: presser::Slab 145 | /// [`copy_from_slice_to_offset`]: presser::copy_from_slice_to_offset 146 | /// [helper functions]: presser#functions 147 | /// [\[1\]]: presser#motivation 148 | #[derive(Debug)] 149 | pub struct Allocation { 150 | chunk_id: Option, 151 | offset: u64, 152 | size: u64, 153 | memory_block_index: usize, 154 | memory_type_index: usize, 155 | device_memory: vk::DeviceMemory, 156 | mapped_ptr: Option, 157 | dedicated_allocation: bool, 158 | memory_properties: vk::MemoryPropertyFlags, 159 | name: Option>, 160 | } 161 | 162 | impl Allocation { 163 | /// Tries to borrow the CPU-mapped memory that backs this allocation as a [`presser::Slab`], which you can then 164 | /// use to safely copy data into the raw, potentially-uninitialized buffer. 165 | /// See [the documentation of Allocation][Allocation#example] for an example of this. 166 | /// 167 | /// Returns [`None`] if `self.mapped_ptr()` is `None`, or if `self.size()` is greater than `isize::MAX` because 168 | /// this could lead to undefined behavior. 169 | /// 170 | /// Note that [`Allocation`] implements [`Slab`] natively, so you can actually pass this allocation as a [`Slab`] 171 | /// directly. However, if `self` is not actually a valid [`Slab`] (this function would return `None` as described above), 172 | /// then trying to use it as a [`Slab`] will panic. 173 | /// 174 | /// # Safety 175 | /// 176 | /// See the note about safety in [the documentation of Allocation][Allocation#safety] 177 | /// 178 | /// [`Slab`]: presser::Slab 179 | // best to be explicit where the lifetime is coming from since we're doing unsafe things 180 | // and relying on an inferred lifetime type in the PhantomData below 181 | #[allow(clippy::needless_lifetimes)] 182 | pub fn try_as_mapped_slab<'a>(&'a mut self) -> Option> { 183 | let mapped_ptr = self.mapped_ptr()?.cast().as_ptr(); 184 | 185 | if self.size > isize::MAX as _ { 186 | return None; 187 | } 188 | 189 | // this will always succeed since size is <= isize::MAX which is < usize::MAX 190 | let size = self.size as usize; 191 | 192 | Some(MappedAllocationSlab { 193 | _borrowed_alloc: PhantomData, 194 | mapped_ptr, 195 | size, 196 | }) 197 | } 198 | 199 | pub fn chunk_id(&self) -> Option { 200 | self.chunk_id 201 | } 202 | 203 | ///Returns the [`vk::MemoryPropertyFlags`] of this allocation. 204 | pub fn memory_properties(&self) -> vk::MemoryPropertyFlags { 205 | self.memory_properties 206 | } 207 | 208 | /// Returns the [`vk::DeviceMemory`] object that is backing this allocation. 209 | /// 210 | /// This memory object can be shared with multiple other allocations and shouldn't be freed or allocated from 211 | /// without this library, because that will lead to undefined behavior. 212 | /// 213 | /// # Safety 214 | /// The result of this function can safely be passed into [`ash::Device::bind_buffer_memory()`], 215 | /// [`ash::Device::bind_image_memory()`] etc. It is exposed for this reason. Keep in mind to 216 | /// also pass [`Self::offset()`] along to those. 217 | /// 218 | /// Also, this [`Allocation`] must not be [`Allocator::free()`]d while such a created resource 219 | /// on this [`vk::DeviceMemory`] is still live. 220 | pub unsafe fn memory(&self) -> vk::DeviceMemory { 221 | self.device_memory 222 | } 223 | 224 | /// Returns [`true`] if this allocation is using a dedicated underlying allocation. 225 | pub fn is_dedicated(&self) -> bool { 226 | self.dedicated_allocation 227 | } 228 | 229 | /// Returns the offset of the allocation on the [`vk::DeviceMemory`]. 230 | /// When binding the memory to a buffer or image, this offset needs to be supplied as well. 231 | pub fn offset(&self) -> u64 { 232 | self.offset 233 | } 234 | 235 | /// Returns the size of the allocation 236 | pub fn size(&self) -> u64 { 237 | self.size 238 | } 239 | 240 | /// Returns a valid mapped pointer if the memory is host visible, otherwise it will return None. 241 | /// The pointer already points to the exact memory region of the suballocation, so no offset needs to be applied. 242 | pub fn mapped_ptr(&self) -> Option> { 243 | self.mapped_ptr.map(|SendSyncPtr(p)| p) 244 | } 245 | 246 | /// Returns a valid mapped slice if the memory is host visible, otherwise it will return None. 247 | /// The slice already references the exact memory region of the allocation, so no offset needs to be applied. 248 | pub fn mapped_slice(&self) -> Option<&[u8]> { 249 | self.mapped_ptr().map(|ptr| unsafe { 250 | std::slice::from_raw_parts(ptr.cast().as_ptr(), self.size as usize) 251 | }) 252 | } 253 | 254 | /// Returns a valid mapped mutable slice if the memory is host visible, otherwise it will return None. 255 | /// The slice already references the exact memory region of the allocation, so no offset needs to be applied. 256 | pub fn mapped_slice_mut(&mut self) -> Option<&mut [u8]> { 257 | self.mapped_ptr().map(|ptr| unsafe { 258 | std::slice::from_raw_parts_mut(ptr.cast().as_ptr(), self.size as usize) 259 | }) 260 | } 261 | 262 | pub fn is_null(&self) -> bool { 263 | self.chunk_id.is_none() 264 | } 265 | } 266 | 267 | impl Default for Allocation { 268 | fn default() -> Self { 269 | Self { 270 | chunk_id: None, 271 | offset: 0, 272 | size: 0, 273 | memory_block_index: !0, 274 | memory_type_index: !0, 275 | device_memory: vk::DeviceMemory::null(), 276 | mapped_ptr: None, 277 | memory_properties: vk::MemoryPropertyFlags::empty(), 278 | name: None, 279 | dedicated_allocation: false, 280 | } 281 | } 282 | } 283 | 284 | /// A wrapper struct over a borrowed [`Allocation`] that infallibly implements [`presser::Slab`]. 285 | /// 286 | /// This type should be acquired by calling [`Allocation::try_as_mapped_slab`]. 287 | pub struct MappedAllocationSlab<'a> { 288 | _borrowed_alloc: PhantomData<&'a mut Allocation>, 289 | mapped_ptr: *mut u8, 290 | size: usize, 291 | } 292 | 293 | // SAFETY: See the safety comment of Allocation::as_mapped_slab above. 294 | unsafe impl presser::Slab for MappedAllocationSlab<'_> { 295 | fn base_ptr(&self) -> *const u8 { 296 | self.mapped_ptr 297 | } 298 | 299 | fn base_ptr_mut(&mut self) -> *mut u8 { 300 | self.mapped_ptr 301 | } 302 | 303 | fn size(&self) -> usize { 304 | self.size 305 | } 306 | } 307 | 308 | // SAFETY: See the safety comment of Allocation::as_mapped_slab above. 309 | unsafe impl presser::Slab for Allocation { 310 | fn base_ptr(&self) -> *const u8 { 311 | self.mapped_ptr 312 | .expect("tried to use a non-mapped Allocation as a Slab") 313 | .0 314 | .as_ptr() 315 | .cast() 316 | } 317 | 318 | fn base_ptr_mut(&mut self) -> *mut u8 { 319 | self.mapped_ptr 320 | .expect("tried to use a non-mapped Allocation as a Slab") 321 | .0 322 | .as_ptr() 323 | .cast() 324 | } 325 | 326 | fn size(&self) -> usize { 327 | if self.size > isize::MAX as _ { 328 | panic!("tried to use an Allocation with size > isize::MAX as a Slab") 329 | } 330 | // this will always work if the above passed 331 | self.size as usize 332 | } 333 | } 334 | 335 | #[derive(Debug)] 336 | pub(crate) struct MemoryBlock { 337 | pub(crate) device_memory: vk::DeviceMemory, 338 | pub(crate) size: u64, 339 | pub(crate) mapped_ptr: Option, 340 | pub(crate) sub_allocator: Box, 341 | #[cfg(feature = "visualizer")] 342 | pub(crate) dedicated_allocation: bool, 343 | } 344 | 345 | impl MemoryBlock { 346 | fn new( 347 | device: &ash::Device, 348 | size: u64, 349 | mem_type_index: usize, 350 | mapped: bool, 351 | buffer_device_address: bool, 352 | allocation_scheme: AllocationScheme, 353 | requires_personal_block: bool, 354 | ) -> Result { 355 | let device_memory = { 356 | let alloc_info = vk::MemoryAllocateInfo::default() 357 | .allocation_size(size) 358 | .memory_type_index(mem_type_index as u32); 359 | 360 | let allocation_flags = vk::MemoryAllocateFlags::DEVICE_ADDRESS; 361 | let mut flags_info = vk::MemoryAllocateFlagsInfo::default().flags(allocation_flags); 362 | // TODO(manon): Test this based on if the device has this feature enabled or not 363 | let alloc_info = if buffer_device_address { 364 | alloc_info.push_next(&mut flags_info) 365 | } else { 366 | alloc_info 367 | }; 368 | 369 | // Flag the memory as dedicated if required. 370 | let mut dedicated_memory_info = vk::MemoryDedicatedAllocateInfo::default(); 371 | let alloc_info = match allocation_scheme { 372 | AllocationScheme::DedicatedBuffer(buffer) => { 373 | dedicated_memory_info = dedicated_memory_info.buffer(buffer); 374 | alloc_info.push_next(&mut dedicated_memory_info) 375 | } 376 | AllocationScheme::DedicatedImage(image) => { 377 | dedicated_memory_info = dedicated_memory_info.image(image); 378 | alloc_info.push_next(&mut dedicated_memory_info) 379 | } 380 | AllocationScheme::GpuAllocatorManaged => alloc_info, 381 | }; 382 | 383 | unsafe { device.allocate_memory(&alloc_info, None) }.map_err(|e| match e { 384 | vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => AllocationError::OutOfMemory, 385 | e => AllocationError::Internal(format!( 386 | "Unexpected error in vkAllocateMemory: {:?}", 387 | e 388 | )), 389 | })? 390 | }; 391 | 392 | let mapped_ptr = mapped 393 | .then(|| { 394 | unsafe { 395 | device.map_memory( 396 | device_memory, 397 | 0, 398 | vk::WHOLE_SIZE, 399 | vk::MemoryMapFlags::empty(), 400 | ) 401 | } 402 | .map_err(|e| { 403 | unsafe { device.free_memory(device_memory, None) }; 404 | AllocationError::FailedToMap(e.to_string()) 405 | }) 406 | .and_then(|p| { 407 | std::ptr::NonNull::new(p).map(SendSyncPtr).ok_or_else(|| { 408 | AllocationError::FailedToMap("Returned mapped pointer is null".to_owned()) 409 | }) 410 | }) 411 | }) 412 | .transpose()?; 413 | 414 | let sub_allocator: Box = if allocation_scheme 415 | != AllocationScheme::GpuAllocatorManaged 416 | || requires_personal_block 417 | { 418 | Box::new(allocator::DedicatedBlockAllocator::new(size)) 419 | } else { 420 | Box::new(allocator::FreeListAllocator::new(size)) 421 | }; 422 | 423 | Ok(Self { 424 | device_memory, 425 | size, 426 | mapped_ptr, 427 | sub_allocator, 428 | #[cfg(feature = "visualizer")] 429 | dedicated_allocation: allocation_scheme != AllocationScheme::GpuAllocatorManaged, 430 | }) 431 | } 432 | 433 | fn destroy(self, device: &ash::Device) { 434 | if self.mapped_ptr.is_some() { 435 | unsafe { device.unmap_memory(self.device_memory) }; 436 | } 437 | 438 | unsafe { device.free_memory(self.device_memory, None) }; 439 | } 440 | } 441 | 442 | #[derive(Debug)] 443 | pub(crate) struct MemoryType { 444 | pub(crate) memory_blocks: Vec>, 445 | pub(crate) memory_properties: vk::MemoryPropertyFlags, 446 | pub(crate) memory_type_index: usize, 447 | pub(crate) heap_index: usize, 448 | pub(crate) mappable: bool, 449 | pub(crate) active_general_blocks: usize, 450 | pub(crate) buffer_device_address: bool, 451 | } 452 | 453 | impl MemoryType { 454 | fn allocate( 455 | &mut self, 456 | device: &ash::Device, 457 | desc: &AllocationCreateDesc<'_>, 458 | granularity: u64, 459 | backtrace: Arc, 460 | allocation_sizes: &AllocationSizes, 461 | ) -> Result { 462 | let allocation_type = if desc.linear { 463 | allocator::AllocationType::Linear 464 | } else { 465 | allocator::AllocationType::NonLinear 466 | }; 467 | 468 | let is_host = self 469 | .memory_properties 470 | .contains(vk::MemoryPropertyFlags::HOST_VISIBLE); 471 | 472 | let memblock_size = allocation_sizes.get_memblock_size(is_host, self.active_general_blocks); 473 | 474 | let size = desc.requirements.size; 475 | let alignment = desc.requirements.alignment; 476 | 477 | let dedicated_allocation = desc.allocation_scheme != AllocationScheme::GpuAllocatorManaged; 478 | let requires_personal_block = size > memblock_size; 479 | 480 | // Create a dedicated block for large memory allocations or allocations that require dedicated memory allocations. 481 | if dedicated_allocation || requires_personal_block { 482 | let mem_block = MemoryBlock::new( 483 | device, 484 | size, 485 | self.memory_type_index, 486 | self.mappable, 487 | self.buffer_device_address, 488 | desc.allocation_scheme, 489 | requires_personal_block, 490 | )?; 491 | 492 | let mut block_index = None; 493 | for (i, block) in self.memory_blocks.iter().enumerate() { 494 | if block.is_none() { 495 | block_index = Some(i); 496 | break; 497 | } 498 | } 499 | 500 | let block_index = match block_index { 501 | Some(i) => { 502 | self.memory_blocks[i].replace(mem_block); 503 | i 504 | } 505 | None => { 506 | self.memory_blocks.push(Some(mem_block)); 507 | self.memory_blocks.len() - 1 508 | } 509 | }; 510 | 511 | let mem_block = self.memory_blocks[block_index] 512 | .as_mut() 513 | .ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?; 514 | 515 | let (offset, chunk_id) = mem_block.sub_allocator.allocate( 516 | size, 517 | alignment, 518 | allocation_type, 519 | granularity, 520 | desc.name, 521 | backtrace, 522 | )?; 523 | 524 | return Ok(Allocation { 525 | chunk_id: Some(chunk_id), 526 | offset, 527 | size, 528 | memory_block_index: block_index, 529 | memory_type_index: self.memory_type_index, 530 | device_memory: mem_block.device_memory, 531 | mapped_ptr: mem_block.mapped_ptr, 532 | memory_properties: self.memory_properties, 533 | name: Some(desc.name.into()), 534 | dedicated_allocation, 535 | }); 536 | } 537 | 538 | let mut empty_block_index = None; 539 | for (mem_block_i, mem_block) in self.memory_blocks.iter_mut().enumerate().rev() { 540 | if let Some(mem_block) = mem_block { 541 | let allocation = mem_block.sub_allocator.allocate( 542 | size, 543 | alignment, 544 | allocation_type, 545 | granularity, 546 | desc.name, 547 | backtrace.clone(), 548 | ); 549 | 550 | match allocation { 551 | Ok((offset, chunk_id)) => { 552 | let mapped_ptr = if let Some(SendSyncPtr(mapped_ptr)) = mem_block.mapped_ptr 553 | { 554 | let offset_ptr = unsafe { mapped_ptr.as_ptr().add(offset as usize) }; 555 | std::ptr::NonNull::new(offset_ptr).map(SendSyncPtr) 556 | } else { 557 | None 558 | }; 559 | return Ok(Allocation { 560 | chunk_id: Some(chunk_id), 561 | offset, 562 | size, 563 | memory_block_index: mem_block_i, 564 | memory_type_index: self.memory_type_index, 565 | device_memory: mem_block.device_memory, 566 | memory_properties: self.memory_properties, 567 | mapped_ptr, 568 | dedicated_allocation: false, 569 | name: Some(desc.name.into()), 570 | }); 571 | } 572 | Err(err) => match err { 573 | AllocationError::OutOfMemory => {} // Block is full, continue search. 574 | _ => return Err(err), // Unhandled error, return. 575 | }, 576 | } 577 | } else if empty_block_index.is_none() { 578 | empty_block_index = Some(mem_block_i); 579 | } 580 | } 581 | 582 | let new_memory_block = MemoryBlock::new( 583 | device, 584 | memblock_size, 585 | self.memory_type_index, 586 | self.mappable, 587 | self.buffer_device_address, 588 | desc.allocation_scheme, 589 | false, 590 | )?; 591 | 592 | let new_block_index = if let Some(block_index) = empty_block_index { 593 | self.memory_blocks[block_index] = Some(new_memory_block); 594 | block_index 595 | } else { 596 | self.memory_blocks.push(Some(new_memory_block)); 597 | self.memory_blocks.len() - 1 598 | }; 599 | 600 | self.active_general_blocks += 1; 601 | 602 | let mem_block = self.memory_blocks[new_block_index] 603 | .as_mut() 604 | .ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?; 605 | let allocation = mem_block.sub_allocator.allocate( 606 | size, 607 | alignment, 608 | allocation_type, 609 | granularity, 610 | desc.name, 611 | backtrace, 612 | ); 613 | let (offset, chunk_id) = match allocation { 614 | Ok(value) => value, 615 | Err(err) => match err { 616 | AllocationError::OutOfMemory => { 617 | return Err(AllocationError::Internal( 618 | "Allocation that must succeed failed. This is a bug in the allocator." 619 | .into(), 620 | )) 621 | } 622 | _ => return Err(err), 623 | }, 624 | }; 625 | 626 | let mapped_ptr = if let Some(SendSyncPtr(mapped_ptr)) = mem_block.mapped_ptr { 627 | let offset_ptr = unsafe { mapped_ptr.as_ptr().add(offset as usize) }; 628 | std::ptr::NonNull::new(offset_ptr).map(SendSyncPtr) 629 | } else { 630 | None 631 | }; 632 | 633 | Ok(Allocation { 634 | chunk_id: Some(chunk_id), 635 | offset, 636 | size, 637 | memory_block_index: new_block_index, 638 | memory_type_index: self.memory_type_index, 639 | device_memory: mem_block.device_memory, 640 | mapped_ptr, 641 | memory_properties: self.memory_properties, 642 | name: Some(desc.name.into()), 643 | dedicated_allocation: false, 644 | }) 645 | } 646 | 647 | #[allow(clippy::needless_pass_by_value)] 648 | fn free(&mut self, allocation: Allocation, device: &ash::Device) -> Result<()> { 649 | let block_idx = allocation.memory_block_index; 650 | 651 | let mem_block = self.memory_blocks[block_idx] 652 | .as_mut() 653 | .ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?; 654 | 655 | mem_block.sub_allocator.free(allocation.chunk_id)?; 656 | 657 | if mem_block.sub_allocator.is_empty() { 658 | if mem_block.sub_allocator.supports_general_allocations() { 659 | if self.active_general_blocks > 1 { 660 | let block = self.memory_blocks[block_idx].take(); 661 | let block = block.ok_or_else(|| { 662 | AllocationError::Internal("Memory block must be Some.".into()) 663 | })?; 664 | block.destroy(device); 665 | 666 | self.active_general_blocks -= 1; 667 | } 668 | } else { 669 | let block = self.memory_blocks[block_idx].take(); 670 | let block = block.ok_or_else(|| { 671 | AllocationError::Internal("Memory block must be Some.".into()) 672 | })?; 673 | block.destroy(device); 674 | } 675 | } 676 | 677 | Ok(()) 678 | } 679 | } 680 | 681 | pub struct Allocator { 682 | pub(crate) memory_types: Vec, 683 | pub(crate) memory_heaps: Vec, 684 | device: ash::Device, 685 | pub(crate) buffer_image_granularity: u64, 686 | pub(crate) debug_settings: AllocatorDebugSettings, 687 | allocation_sizes: AllocationSizes, 688 | } 689 | 690 | impl fmt::Debug for Allocator { 691 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 692 | self.generate_report().fmt(f) 693 | } 694 | } 695 | 696 | impl Allocator { 697 | pub fn new(desc: &AllocatorCreateDesc) -> Result { 698 | if desc.physical_device == vk::PhysicalDevice::null() { 699 | return Err(AllocationError::InvalidAllocatorCreateDesc( 700 | "AllocatorCreateDesc field `physical_device` is null.".into(), 701 | )); 702 | } 703 | 704 | let mem_props = unsafe { 705 | desc.instance 706 | .get_physical_device_memory_properties(desc.physical_device) 707 | }; 708 | 709 | let memory_types = &mem_props.memory_types_as_slice(); 710 | let memory_heaps = mem_props.memory_heaps_as_slice().to_vec(); 711 | 712 | if desc.debug_settings.log_memory_information { 713 | debug!("memory type count: {}", mem_props.memory_type_count); 714 | debug!("memory heap count: {}", mem_props.memory_heap_count); 715 | 716 | for (i, mem_type) in memory_types.iter().enumerate() { 717 | let flags = mem_type.property_flags; 718 | debug!( 719 | "memory type[{}]: prop flags: 0x{:x}, heap[{}]", 720 | i, 721 | flags.as_raw(), 722 | mem_type.heap_index, 723 | ); 724 | } 725 | for (i, heap) in memory_heaps.iter().enumerate() { 726 | debug!( 727 | "heap[{}] flags: 0x{:x}, size: {} MiB", 728 | i, 729 | heap.flags.as_raw(), 730 | heap.size / (1024 * 1024) 731 | ); 732 | } 733 | } 734 | 735 | let memory_types = memory_types 736 | .iter() 737 | .enumerate() 738 | .map(|(i, mem_type)| MemoryType { 739 | memory_blocks: Vec::default(), 740 | memory_properties: mem_type.property_flags, 741 | memory_type_index: i, 742 | heap_index: mem_type.heap_index as usize, 743 | mappable: mem_type 744 | .property_flags 745 | .contains(vk::MemoryPropertyFlags::HOST_VISIBLE), 746 | active_general_blocks: 0, 747 | buffer_device_address: desc.buffer_device_address, 748 | }) 749 | .collect::>(); 750 | 751 | let physical_device_properties = unsafe { 752 | desc.instance 753 | .get_physical_device_properties(desc.physical_device) 754 | }; 755 | 756 | let granularity = physical_device_properties.limits.buffer_image_granularity; 757 | 758 | Ok(Self { 759 | memory_types, 760 | memory_heaps, 761 | device: desc.device.clone(), 762 | buffer_image_granularity: granularity, 763 | debug_settings: desc.debug_settings, 764 | allocation_sizes: desc.allocation_sizes, 765 | }) 766 | } 767 | 768 | pub fn allocate(&mut self, desc: &AllocationCreateDesc<'_>) -> Result { 769 | let size = desc.requirements.size; 770 | let alignment = desc.requirements.alignment; 771 | 772 | let backtrace = Arc::new(if self.debug_settings.store_stack_traces { 773 | Backtrace::force_capture() 774 | } else { 775 | Backtrace::disabled() 776 | }); 777 | 778 | if self.debug_settings.log_allocations { 779 | debug!( 780 | "Allocating `{}` of {} bytes with an alignment of {}.", 781 | &desc.name, size, alignment 782 | ); 783 | if self.debug_settings.log_stack_traces { 784 | let backtrace = Backtrace::force_capture(); 785 | debug!("Allocation stack trace: {}", backtrace); 786 | } 787 | } 788 | 789 | if size == 0 || !alignment.is_power_of_two() { 790 | return Err(AllocationError::InvalidAllocationCreateDesc); 791 | } 792 | 793 | let mem_loc_preferred_bits = match desc.location { 794 | MemoryLocation::GpuOnly => vk::MemoryPropertyFlags::DEVICE_LOCAL, 795 | MemoryLocation::CpuToGpu => { 796 | vk::MemoryPropertyFlags::HOST_VISIBLE 797 | | vk::MemoryPropertyFlags::HOST_COHERENT 798 | | vk::MemoryPropertyFlags::DEVICE_LOCAL 799 | } 800 | MemoryLocation::GpuToCpu => { 801 | vk::MemoryPropertyFlags::HOST_VISIBLE 802 | | vk::MemoryPropertyFlags::HOST_COHERENT 803 | | vk::MemoryPropertyFlags::HOST_CACHED 804 | } 805 | MemoryLocation::Unknown => vk::MemoryPropertyFlags::empty(), 806 | }; 807 | let mut memory_type_index_opt = 808 | self.find_memorytype_index(&desc.requirements, mem_loc_preferred_bits); 809 | 810 | if memory_type_index_opt.is_none() { 811 | let mem_loc_required_bits = match desc.location { 812 | MemoryLocation::GpuOnly => vk::MemoryPropertyFlags::DEVICE_LOCAL, 813 | MemoryLocation::CpuToGpu | MemoryLocation::GpuToCpu => { 814 | vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT 815 | } 816 | MemoryLocation::Unknown => vk::MemoryPropertyFlags::empty(), 817 | }; 818 | 819 | memory_type_index_opt = 820 | self.find_memorytype_index(&desc.requirements, mem_loc_required_bits); 821 | } 822 | 823 | let memory_type_index = match memory_type_index_opt { 824 | Some(x) => x as usize, 825 | None => return Err(AllocationError::NoCompatibleMemoryTypeFound), 826 | }; 827 | 828 | //Do not try to create a block if the heap is smaller than the required size (avoids validation warnings). 829 | let memory_type = &mut self.memory_types[memory_type_index]; 830 | let allocation = if size > self.memory_heaps[memory_type.heap_index].size { 831 | Err(AllocationError::OutOfMemory) 832 | } else { 833 | memory_type.allocate( 834 | &self.device, 835 | desc, 836 | self.buffer_image_granularity, 837 | backtrace.clone(), 838 | &self.allocation_sizes, 839 | ) 840 | }; 841 | 842 | if desc.location == MemoryLocation::CpuToGpu { 843 | if allocation.is_err() { 844 | let mem_loc_preferred_bits = 845 | vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT; 846 | 847 | let memory_type_index_opt = 848 | self.find_memorytype_index(&desc.requirements, mem_loc_preferred_bits); 849 | 850 | let memory_type_index = match memory_type_index_opt { 851 | Some(x) => x as usize, 852 | None => return Err(AllocationError::NoCompatibleMemoryTypeFound), 853 | }; 854 | 855 | self.memory_types[memory_type_index].allocate( 856 | &self.device, 857 | desc, 858 | self.buffer_image_granularity, 859 | backtrace, 860 | &self.allocation_sizes, 861 | ) 862 | } else { 863 | allocation 864 | } 865 | } else { 866 | allocation 867 | } 868 | } 869 | 870 | pub fn free(&mut self, allocation: Allocation) -> Result<()> { 871 | if self.debug_settings.log_frees { 872 | let name = allocation.name.as_deref().unwrap_or(""); 873 | debug!("Freeing `{}`.", name); 874 | if self.debug_settings.log_stack_traces { 875 | let backtrace = Backtrace::force_capture(); 876 | debug!("Free stack trace: {}", backtrace); 877 | } 878 | } 879 | 880 | if allocation.is_null() { 881 | return Ok(()); 882 | } 883 | 884 | self.memory_types[allocation.memory_type_index].free(allocation, &self.device)?; 885 | 886 | Ok(()) 887 | } 888 | 889 | pub fn rename_allocation(&mut self, allocation: &mut Allocation, name: &str) -> Result<()> { 890 | allocation.name = Some(name.into()); 891 | 892 | if allocation.is_null() { 893 | return Ok(()); 894 | } 895 | 896 | let mem_type = &mut self.memory_types[allocation.memory_type_index]; 897 | let mem_block = mem_type.memory_blocks[allocation.memory_block_index] 898 | .as_mut() 899 | .ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?; 900 | 901 | mem_block 902 | .sub_allocator 903 | .rename_allocation(allocation.chunk_id, name)?; 904 | 905 | Ok(()) 906 | } 907 | 908 | pub fn report_memory_leaks(&self, log_level: Level) { 909 | for (mem_type_i, mem_type) in self.memory_types.iter().enumerate() { 910 | for (block_i, mem_block) in mem_type.memory_blocks.iter().enumerate() { 911 | if let Some(mem_block) = mem_block { 912 | mem_block 913 | .sub_allocator 914 | .report_memory_leaks(log_level, mem_type_i, block_i); 915 | } 916 | } 917 | } 918 | } 919 | 920 | fn find_memorytype_index( 921 | &self, 922 | memory_req: &vk::MemoryRequirements, 923 | flags: vk::MemoryPropertyFlags, 924 | ) -> Option { 925 | self.memory_types 926 | .iter() 927 | .find(|memory_type| { 928 | (1 << memory_type.memory_type_index) & memory_req.memory_type_bits != 0 929 | && memory_type.memory_properties.contains(flags) 930 | }) 931 | .map(|memory_type| memory_type.memory_type_index as _) 932 | } 933 | 934 | pub fn generate_report(&self) -> AllocatorReport { 935 | let mut allocations = vec![]; 936 | let mut blocks = vec![]; 937 | let mut total_capacity_bytes = 0; 938 | 939 | for memory_type in &self.memory_types { 940 | for block in memory_type.memory_blocks.iter().flatten() { 941 | total_capacity_bytes += block.size; 942 | let first_allocation = allocations.len(); 943 | allocations.extend(block.sub_allocator.report_allocations()); 944 | blocks.push(MemoryBlockReport { 945 | size: block.size, 946 | allocations: first_allocation..allocations.len(), 947 | }); 948 | } 949 | } 950 | 951 | let total_allocated_bytes = allocations.iter().map(|report| report.size).sum(); 952 | 953 | AllocatorReport { 954 | allocations, 955 | blocks, 956 | total_allocated_bytes, 957 | total_capacity_bytes, 958 | } 959 | } 960 | 961 | /// Current total capacity of memory blocks allocated on the device, in bytes 962 | pub fn capacity(&self) -> u64 { 963 | let mut total_capacity_bytes = 0; 964 | 965 | for memory_type in &self.memory_types { 966 | for block in memory_type.memory_blocks.iter().flatten() { 967 | total_capacity_bytes += block.size; 968 | } 969 | } 970 | 971 | total_capacity_bytes 972 | } 973 | } 974 | 975 | impl Drop for Allocator { 976 | fn drop(&mut self) { 977 | if self.debug_settings.log_leaks_on_shutdown { 978 | self.report_memory_leaks(Level::Warn); 979 | } 980 | 981 | // Free all remaining memory blocks 982 | for mem_type in self.memory_types.iter_mut() { 983 | for mem_block in mem_type.memory_blocks.iter_mut() { 984 | let block = mem_block.take(); 985 | if let Some(block) = block { 986 | block.destroy(&self.device); 987 | } 988 | } 989 | } 990 | } 991 | } 992 | -------------------------------------------------------------------------------- /src/vulkan/visualizer.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::new_without_default)] 2 | 3 | use super::Allocator; 4 | use crate::visualizer::{ 5 | render_allocation_reports_ui, AllocationReportVisualizeSettings, ColorScheme, 6 | MemoryChunksVisualizationSettings, 7 | }; 8 | 9 | struct AllocatorVisualizerBlockWindow { 10 | memory_type_index: usize, 11 | block_index: usize, 12 | settings: MemoryChunksVisualizationSettings, 13 | } 14 | impl AllocatorVisualizerBlockWindow { 15 | fn new(memory_type_index: usize, block_index: usize) -> Self { 16 | Self { 17 | memory_type_index, 18 | block_index, 19 | settings: Default::default(), 20 | } 21 | } 22 | } 23 | 24 | pub struct AllocatorVisualizer { 25 | selected_blocks: Vec, 26 | color_scheme: ColorScheme, 27 | breakdown_settings: AllocationReportVisualizeSettings, 28 | } 29 | 30 | impl AllocatorVisualizer { 31 | pub fn new() -> Self { 32 | Self { 33 | selected_blocks: Vec::default(), 34 | color_scheme: ColorScheme::default(), 35 | breakdown_settings: Default::default(), 36 | } 37 | } 38 | 39 | pub fn set_color_scheme(&mut self, color_scheme: ColorScheme) { 40 | self.color_scheme = color_scheme; 41 | } 42 | 43 | pub fn render_memory_block_ui(&mut self, ui: &mut egui::Ui, alloc: &Allocator) { 44 | ui.label(format!( 45 | "buffer image granularity: {:?}", 46 | alloc.buffer_image_granularity 47 | )); 48 | 49 | ui.collapsing( 50 | format!("Memory Heaps ({} heaps)", alloc.memory_heaps.len()), 51 | |ui| { 52 | for (i, heap) in alloc.memory_heaps.iter().enumerate() { 53 | ui.collapsing(format!("Heap: {}", i), |ui| { 54 | ui.label(format!("flags: {:?}", heap.flags)); 55 | ui.label(format!( 56 | "size: {} MiB", 57 | heap.size as f64 / (1024 * 1024) as f64 58 | )); 59 | }); 60 | } 61 | }, 62 | ); 63 | 64 | ui.collapsing( 65 | format!("Memory Types: ({} types)", alloc.memory_types.len()), 66 | |ui| { 67 | for (mem_type_idx, mem_type) in alloc.memory_types.iter().enumerate() { 68 | ui.collapsing( 69 | format!( 70 | "Type: {} ({} blocks)", 71 | mem_type_idx, 72 | mem_type.memory_blocks.len(), 73 | ), 74 | |ui| { 75 | let mut total_block_size = 0; 76 | let mut total_allocated = 0; 77 | 78 | for block in mem_type.memory_blocks.iter().flatten() { 79 | total_block_size += block.size; 80 | total_allocated += block.sub_allocator.allocated(); 81 | } 82 | 83 | let active_block_count = mem_type 84 | .memory_blocks 85 | .iter() 86 | .filter(|block| block.is_some()) 87 | .count(); 88 | 89 | ui.label(format!("properties: {:?}", mem_type.memory_properties)); 90 | ui.label(format!("heap index: {}", mem_type.heap_index)); 91 | ui.label(format!("total block size: {} KiB", total_block_size / 1024)); 92 | ui.label(format!("total allocated: {} KiB", total_allocated / 1024)); 93 | ui.label(format!("block count: {}", active_block_count)); 94 | 95 | for (block_idx, block) in mem_type.memory_blocks.iter().enumerate() { 96 | let Some(block) = block else { continue }; 97 | 98 | ui.collapsing(format!("Block: {}", block_idx), |ui| { 99 | use ash::vk::Handle; 100 | 101 | ui.label(format!("size: {} KiB", block.size / 1024)); 102 | ui.label(format!( 103 | "allocated: {} KiB", 104 | block.sub_allocator.allocated() / 1024 105 | )); 106 | ui.label(format!( 107 | "vk device memory: 0x{:x}", 108 | block.device_memory.as_raw() 109 | )); 110 | if let Some(mapped_ptr) = block.mapped_ptr { 111 | ui.label(format!( 112 | "mapped pointer: {:#p}", 113 | mapped_ptr.0.as_ptr() 114 | )); 115 | } 116 | if block.dedicated_allocation { 117 | ui.label("Dedicated Allocation"); 118 | } 119 | 120 | block.sub_allocator.draw_base_info(ui); 121 | 122 | if block.sub_allocator.supports_visualization() 123 | && ui.button("visualize").clicked() 124 | && !self.selected_blocks.iter().any(|x| { 125 | x.memory_type_index == mem_type_idx 126 | && x.block_index == block_idx 127 | }) 128 | { 129 | self.selected_blocks.push( 130 | AllocatorVisualizerBlockWindow::new( 131 | mem_type_idx, 132 | block_idx, 133 | ), 134 | ); 135 | } 136 | }); 137 | } 138 | }, 139 | ); 140 | } 141 | }, 142 | ); 143 | } 144 | 145 | pub fn render_memory_block_window( 146 | &mut self, 147 | ctx: &egui::Context, 148 | allocator: &Allocator, 149 | open: &mut bool, 150 | ) { 151 | egui::Window::new("Allocator Memory Blocks") 152 | .open(open) 153 | .show(ctx, |ui| self.render_breakdown_ui(ui, allocator)); 154 | } 155 | 156 | pub fn render_memory_block_visualization_windows( 157 | &mut self, 158 | ctx: &egui::Context, 159 | allocator: &Allocator, 160 | ) { 161 | // Draw each window. 162 | let color_scheme = &self.color_scheme; 163 | 164 | self.selected_blocks.retain_mut(|window| { 165 | let mut open = true; 166 | 167 | egui::Window::new(format!( 168 | "Block Visualizer {}:{}", 169 | window.memory_type_index, window.block_index 170 | )) 171 | .default_size([1920.0 * 0.5, 1080.0 * 0.5]) 172 | .open(&mut open) 173 | .show(ctx, |ui| { 174 | let memblock = &allocator.memory_types[window.memory_type_index].memory_blocks 175 | [window.block_index] 176 | .as_ref(); 177 | if let Some(memblock) = memblock { 178 | ui.label(format!( 179 | "Memory type {}, Memory block {}, Block size: {} KiB", 180 | window.memory_type_index, 181 | window.block_index, 182 | memblock.size / 1024 183 | )); 184 | 185 | window 186 | .settings 187 | .ui(ui, allocator.debug_settings.store_stack_traces); 188 | 189 | ui.separator(); 190 | 191 | memblock 192 | .sub_allocator 193 | .draw_visualization(color_scheme, ui, &window.settings); 194 | } else { 195 | ui.label("Deallocated memory block"); 196 | } 197 | }); 198 | 199 | open 200 | }); 201 | } 202 | 203 | pub fn render_breakdown_ui(&mut self, ui: &mut egui::Ui, allocator: &Allocator) { 204 | render_allocation_reports_ui( 205 | ui, 206 | &mut self.breakdown_settings, 207 | allocator 208 | .memory_types 209 | .iter() 210 | .flat_map(|memory_type| memory_type.memory_blocks.iter()) 211 | .flatten() 212 | .flat_map(|memory_block| memory_block.sub_allocator.report_allocations()), 213 | ); 214 | } 215 | 216 | pub fn render_breakdown_window( 217 | &mut self, 218 | ctx: &egui::Context, 219 | allocator: &Allocator, 220 | open: &mut bool, 221 | ) { 222 | egui::Window::new("Allocator Breakdown") 223 | .open(open) 224 | .show(ctx, |ui| self.render_breakdown_ui(ui, allocator)); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /visualizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Traverse-Research/gpu-allocator/507451d387c8f281f91efdd53b5d8a6334213dfb/visualizer.png --------------------------------------------------------------------------------